Skip to content

Commit b6caf7a

Browse files
committed
experimental
1 parent 60b011c commit b6caf7a

File tree

2 files changed

+349
-0
lines changed

2 files changed

+349
-0
lines changed

graphql/api.py

Lines changed: 192 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,192 @@
1+
import graphql.type
2+
3+
__all__ = ['Schema']
4+
5+
6+
class PublicType(object):
7+
pass
8+
9+
10+
def build_typeref(refspec):
11+
if isinstance(refspec, TypeRef):
12+
# already a TypeRef
13+
return refspec
14+
15+
if isinstance(refspec, basestring):
16+
# type name reference: Field('SomeType')
17+
return NameTypeRef(refspec)
18+
try:
19+
if issubclass(refspec, PublicType):
20+
# public type reference: Field(SomeType)
21+
return PublicTypeRef(refspec)
22+
except TypeError:
23+
# ignore if refspec is not a class
24+
pass
25+
26+
# internal GraphQL type reference: Field(String)
27+
return InternalTypeRef(refspec)
28+
29+
30+
class TypeRef(object):
31+
def resolve(self, schema):
32+
raise NotImplemented
33+
34+
35+
class NameTypeRef(TypeRef):
36+
def __init__(self, name):
37+
self.name = name
38+
39+
def resolve(self, schema):
40+
return schema._internal_types[self.name]
41+
42+
43+
class WrappingTypeRef(TypeRef):
44+
def __init__(self, wrapper_type, inner_typeref):
45+
self.wrapper_type = wrapper_type
46+
self.inner_typeref = inner_typeref
47+
48+
def resolve(self, schema):
49+
return self.wrapper_type(self.inner_typeref.resolve(schema))
50+
51+
@classmethod
52+
def factory(cls, wrapper_type):
53+
def factory_func(inner_typerefspec):
54+
return cls(wrapper_type, build_typeref(inner_typerefspec))
55+
return factory_func
56+
57+
58+
class PublicTypeRef(TypeRef):
59+
def __init__(self, public_type):
60+
self.public_type = public_type
61+
62+
def resolve(self, schema):
63+
return schema._public_types[self.public_type]
64+
65+
66+
class InternalTypeRef(TypeRef):
67+
def __init__(self, internal_type):
68+
self.internal_type = internal_type
69+
70+
def resolve(self, schema):
71+
return self.internal_type
72+
73+
74+
class LazyField(object):
75+
def __init__(self, typerefspec, args=None, resolver=None,
76+
deprecation_reason=None, description=None):
77+
self.typeref = build_typeref(typerefspec)
78+
self.args = args
79+
self.resolver = resolver
80+
self.deprecation_reason = deprecation_reason
81+
self.description = description
82+
83+
def resolve(self, schema):
84+
return graphql.type.GraphQLField(
85+
self.typeref.resolve(schema),
86+
self.args, self.resolver, self.deprecation_reason, self.description
87+
)
88+
89+
90+
class Schema(object):
91+
String = InternalTypeRef(graphql.type.GraphQLString)
92+
Int = InternalTypeRef(graphql.type.GraphQLInt)
93+
Float = InternalTypeRef(graphql.type.GraphQLFloat)
94+
ID = InternalTypeRef(graphql.type.GraphQLID)
95+
96+
Field = LazyField
97+
EnumValue = graphql.type.GraphQLEnumValue
98+
99+
def __init__(self):
100+
self._internal_types = {}
101+
self._public_types = {}
102+
self._query_root = None
103+
self._mutation_root = None
104+
105+
# Define in the constructor to make functions unbound
106+
self.NonNull = WrappingTypeRef.factory(graphql.type.GraphQLNonNull)
107+
self.List = WrappingTypeRef.factory(graphql.type.GraphQLList)
108+
109+
self.EnumType = self._build_type_definer(self._define_enum)
110+
self.InterfaceType = self._build_type_definer(self._define_interface)
111+
self.ObjectType = self._build_type_definer(self._define_object)
112+
self.QueryRoot = self._build_type_definer(self._define_query_root)
113+
self.MutationRoot = self._build_type_definer(self._define_mutation_root)
114+
115+
def _define_enum(self, dct):
116+
values = {}
117+
for k, v in dct.items():
118+
if isinstance(v, self.EnumValue):
119+
values[k] = v
120+
return graphql.type.GraphQLEnumType(
121+
name=dct['__typename__'],
122+
values=values,
123+
description=dct.get('__doc__'),
124+
)
125+
126+
def _define_interface(self, dct):
127+
fields = {}
128+
for k, v in dct.items():
129+
if isinstance(v, self.Field):
130+
fields[k] = v
131+
return graphql.type.GraphQLInterfaceType(
132+
name=dct['__typename__'],
133+
fields=lambda: self._resolve_fields(fields),
134+
description=dct.get('__doc__'),
135+
)
136+
137+
def _define_object(self, dct):
138+
fields = {}
139+
for k, v in dct.items():
140+
if isinstance(v, self.Field):
141+
fields[k] = v
142+
interfaces = dct.get('__interfaces__')
143+
if interfaces:
144+
interfaces = [self._public_types[public_type] for public_type in interfaces]
145+
return graphql.type.GraphQLObjectType(
146+
name=dct['__typename__'],
147+
fields=lambda: self._resolve_fields(fields),
148+
interfaces=interfaces,
149+
description=dct.get('__doc__'),
150+
)
151+
152+
def _define_query_root(self, dct):
153+
assert not self._query_root
154+
internal_type = self._define_object(dct)
155+
self._query_root = internal_type
156+
return internal_type
157+
158+
def _define_mutation_root(self, dct):
159+
assert not self._mutation_root
160+
internal_type = self._define_object(dct)
161+
self._mutation_root = internal_type
162+
return internal_type
163+
164+
def _resolve_fields(self, fields):
165+
resolved = {}
166+
for k, v in fields.items():
167+
resolved[k] = v.resolve(self)
168+
return resolved
169+
170+
def _build_type_definer(self, internal_type_builder):
171+
class TypeDefinerMeta(type):
172+
def __init__(cls, name, bases, dct):
173+
self._define_type(cls, name, bases, dct, internal_type_builder)
174+
type.__init__(cls, name, bases, dct)
175+
176+
class TypeDefiner(PublicType):
177+
__metaclass__ = TypeDefinerMeta
178+
179+
return TypeDefiner
180+
181+
def _define_type(self, cls, name, bases, dct, internal_type_builder):
182+
if not bases or bases[-1] is PublicType:
183+
return
184+
if '__typename__' not in dct:
185+
dct['__typename__'] = name
186+
assert dct['__typename__'] not in self._internal_types
187+
internal_type = internal_type_builder(dct)
188+
self._internal_types[dct['__typename__']] = internal_type
189+
self._public_types[cls] = internal_type
190+
191+
def to_internal(self):
192+
return graphql.type.GraphQLSchema(self._query_root)

tests/test_api.py

Lines changed: 157 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,157 @@
1+
from pytest import raises
2+
from graphql import graphql
3+
from graphql.api import Schema
4+
5+
gql = Schema()
6+
7+
8+
class Character(gql.InterfaceType):
9+
"""A character in the Star Wars Trilogy"""
10+
id = gql.Field(
11+
gql.NonNull(gql.String),
12+
description='The id of the character.'
13+
)
14+
name = gql.Field(
15+
gql.String,
16+
description='The name of the character.'
17+
)
18+
friends = gql.Field(
19+
gql.List('Character'),
20+
description='The friends of the character, or an empty list if they have none.'
21+
)
22+
appearsIn = gql.Field(
23+
gql.List('Episode'),
24+
description='Which movies they appear in.'
25+
)
26+
27+
28+
class Human(gql.ObjectType):
29+
"""A humanoid creature in the Star Wars universe."""
30+
__interfaces__ = [Character] # Interfaces should be declared first
31+
id = gql.Field(
32+
gql.NonNull(gql.String),
33+
description='The id of the character.'
34+
)
35+
name = gql.Field(
36+
gql.String,
37+
description='The name of the character.'
38+
)
39+
friends = gql.Field(
40+
gql.List('Character'),
41+
description='The friends of the character, or an empty list if they have none.'
42+
)
43+
appearsIn = gql.Field(
44+
gql.List('Episode'),
45+
description='Which movies they appear in.'
46+
)
47+
homePlanet = gql.Field(
48+
gql.String
49+
)
50+
51+
52+
def sort_lists(value):
53+
if isinstance(value, dict):
54+
new_mapping = {}
55+
for k, v in value.iteritems():
56+
new_mapping[k] = sort_lists(v)
57+
return new_mapping
58+
elif isinstance(value, list):
59+
return sorted(map(sort_lists, value))
60+
return value
61+
62+
63+
def test_define_enum_type():
64+
gql = Schema()
65+
66+
class EpisodeEnum(gql.EnumType):
67+
"""One of the films in the Star Wars Trilogy"""
68+
__typename__ = 'Episode'
69+
NEWHOPE = gql.EnumValue(4, description='Released in 1977.')
70+
EMPIRE = gql.EnumValue(5, description='Released in 1980.')
71+
JEDI = gql.EnumValue(6, description='Released in 1983.')
72+
73+
class QueryRoot(gql.QueryRoot):
74+
episode = gql.Field(EpisodeEnum)
75+
76+
result = graphql(gql.to_internal(), '''{
77+
type: __type(name: "Episode") {
78+
name
79+
description
80+
enumValues { name, description }
81+
}
82+
}''')
83+
assert not result.errors
84+
assert sort_lists(result.data) == sort_lists({
85+
"type": {
86+
"name": "Episode",
87+
"description": "One of the films in the Star Wars Trilogy",
88+
"enumValues": [
89+
{"name": "NEWHOPE", "description": "Released in 1977."},
90+
{"name": "EMPIRE", "description": "Released in 1980."},
91+
{"name": "JEDI", "description": "Released in 1983."},
92+
]
93+
}
94+
})
95+
96+
97+
def test_define_object_type():
98+
gql = Schema()
99+
100+
class EpisodeEnum(gql.EnumType):
101+
"""One of the films in the Star Wars Trilogy"""
102+
__typename__ = 'Episode'
103+
NEWHOPE = gql.EnumValue(4, description='Released in 1977.')
104+
EMPIRE = gql.EnumValue(5, description='Released in 1980.')
105+
JEDI = gql.EnumValue(6, description='Released in 1983.')
106+
107+
class QueryRoot(gql.QueryRoot):
108+
"""description"""
109+
byPublicType = gql.Field(EpisodeEnum)
110+
byName = gql.Field('Episode')
111+
byInternalType = gql.Field(gql.String)
112+
byPublicTypeWrapped = gql.Field(gql.List(EpisodeEnum))
113+
byNameWrapped = gql.Field(gql.List('Episode'))
114+
byInternalTypeWrapped = gql.Field(gql.List(gql.String))
115+
116+
result = graphql(gql.to_internal(), '''{
117+
type: __type(name: "QueryRoot") {
118+
name
119+
description
120+
fields { name, type { ...TypeRef } }
121+
}
122+
}
123+
fragment TypeRef on __Type {
124+
kind, name
125+
ofType {
126+
kind, name
127+
ofType { kind, name, ofType }
128+
}
129+
}''')
130+
assert not result.errors
131+
episode_type = {"kind": "ENUM", "name": "Episode", "ofType": None}
132+
string_type = {"kind": "SCALAR", "name": "String", "ofType": None}
133+
assert sort_lists(result.data) == sort_lists({
134+
"type": {
135+
"name": "QueryRoot",
136+
"description": "description",
137+
"fields": [
138+
{"name": "byPublicType", "type": episode_type},
139+
{"name": "byName", "type": episode_type},
140+
{"name": "byInternalType", "type": string_type},
141+
{"name": "byPublicTypeWrapped", "type": {"kind": "LIST", "name": None, "ofType": episode_type}},
142+
{"name": "byNameWrapped", "type": {"kind": "LIST", "name": None, "ofType": episode_type}},
143+
{"name": "byInternalTypeWrapped", "type": {"kind": "LIST", "name": None, "ofType": string_type}},
144+
]
145+
}
146+
})
147+
148+
149+
def test_prevent_defining_many_query_roots():
150+
gql = Schema()
151+
152+
class QueryRoot(gql.QueryRoot):
153+
pass
154+
155+
with raises(Exception):
156+
class SecondQueryRoot(gql.QueryRoot):
157+
pass

0 commit comments

Comments
 (0)