Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 2 additions & 3 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,10 @@ python:
- pypy3
cache: pip
install:
- pip install -e .[test]
- pip install flake8==3.7.9
- pip install -e .[dev]
script:
- flake8 gql tests
- pytest --cov=gql tests
- pytest --cov-report=term-missing --cov=gql tests
after_success:
- coveralls
deploy:
Expand Down
61 changes: 21 additions & 40 deletions gql/dsl.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
import decimal
from functools import partial

import six
from graphql.language import ast
from graphql.language.printer import print_ast
from graphql.type import (GraphQLField, GraphQLList,
GraphQLNonNull, GraphQLEnumType)
from graphql.type import (GraphQLEnumType, GraphQLList, GraphQLNonNull)
from graphql.utils.ast_from_value import ast_from_value

from .utils import to_camel_case

Expand All @@ -31,7 +30,7 @@ def query(self, *args, **kwargs):
return self.execute(query(*args, **kwargs))

def mutate(self, *args, **kwargs):
return self.query(*args, operation='mutate', **kwargs)
return self.query(*args, operation='mutation', **kwargs)

def execute(self, document):
return self.client.execute(document)
Expand All @@ -54,26 +53,12 @@ def get_field(self, name):
if camel_cased_name in self.type.fields:
return camel_cased_name, self.type.fields[camel_cased_name]

raise KeyError('Field {} doesnt exist in type {}.'.format(name, self.type.name))
raise KeyError('Field {} does not exist in type {}.'.format(name, self.type.name))


def selections(*fields):
for _field in fields:
yield field(_field).ast


def get_ast_value(value):
if isinstance(value, ast.Node):
return value
if isinstance(value, six.string_types):
return ast.StringValue(value=value)
elif isinstance(value, bool):
return ast.BooleanValue(value=value)
elif isinstance(value, (float, decimal.Decimal)):
return ast.FloatValue(value=value)
elif isinstance(value, int):
return ast.IntValue(value=value)
return None
yield selection_field(_field).ast


class DSLField(object):
Expand All @@ -89,22 +74,22 @@ def select(self, *fields):
self.ast_field.selection_set.selections.extend(selections(*fields))
return self

def __call__(self, *args, **kwargs):
return self.args(*args, **kwargs)
def __call__(self, **kwargs):
return self.args(**kwargs)

def alias(self, alias):
self.ast_field.alias = ast.Name(value=alias)
return self

def args(self, **args):
for name, value in args.items():
def args(self, **kwargs):
for name, value in kwargs.items():
arg = self.field.args.get(name)
arg_type_serializer = get_arg_serializer(arg.type)
value = arg_type_serializer(value)
serialized_value = arg_type_serializer(value)
self.ast_field.arguments.append(
ast.Argument(
name=ast.Name(value=name),
value=get_ast_value(value)
value=serialized_value
)
)
return self
Expand All @@ -117,29 +102,29 @@ def __str__(self):
return print_ast(self.ast_field)


def field(field, **args):
if isinstance(field, GraphQLField):
return DSLField(field).args(**args)
elif isinstance(field, DSLField):
def selection_field(field):
if isinstance(field, DSLField):
return field

raise Exception('Received incompatible query field: "{}".'.format(field))


def query(*fields):
def query(*fields, **kwargs):
if 'operation' not in kwargs:
kwargs['operation'] = 'query'
return ast.Document(
definitions=[ast.OperationDefinition(
operation='query',
operation=kwargs['operation'],
selection_set=ast.SelectionSet(
selections=list(selections(*fields))
)
)]
)


def serialize_list(serializer, values):
assert isinstance(values, Iterable), 'Expected iterable, received "{}"'.format(repr(values))
return [serializer(v) for v in values]
def serialize_list(serializer, list_values):
assert isinstance(list_values, Iterable), 'Expected iterable, received "{}"'.format(repr(list_values))
return ast.ListValue(values=[serializer(v) for v in list_values])


def get_arg_serializer(arg_type):
Expand All @@ -150,8 +135,4 @@ def get_arg_serializer(arg_type):
return partial(serialize_list, inner_serializer)
if isinstance(arg_type, GraphQLEnumType):
return lambda value: ast.EnumValue(value=arg_type.serialize(value))
return arg_type.serialize


def var(name):
return ast.Variable(name=name)
return lambda value: ast_from_value(arg_type.serialize(value))
1 change: 1 addition & 0 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
]

tests_require = [
'coveralls==1.11.1',
'pytest==4.6.9',
'pytest-cov==2.8.1',
'mock==3.0.5',
Expand Down
17 changes: 16 additions & 1 deletion tests/starwars/fixtures.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
from collections import namedtuple

Human = namedtuple('Human', 'id name friends appearsIn homePlanet')
Human = namedtuple('Human', 'id name friends appearsIn homePlanet isAlive')

luke = Human(
id='1000',
name='Luke Skywalker',
friends=['1002', '1003', '2000', '2001'],
appearsIn=[4, 5, 6],
homePlanet='Tatooine',
isAlive=True,
)

vader = Human(
Expand All @@ -16,6 +17,7 @@
friends=['1004'],
appearsIn=[4, 5, 6],
homePlanet='Tatooine',
isAlive=False,
)

han = Human(
Expand All @@ -24,6 +26,7 @@
friends=['1000', '1003', '2001'],
appearsIn=[4, 5, 6],
homePlanet=None,
isAlive=True,
)

leia = Human(
Expand All @@ -32,6 +35,7 @@
friends=['1000', '1002', '2000', '2001'],
appearsIn=[4, 5, 6],
homePlanet='Alderaan',
isAlive=True,
)

tarkin = Human(
Expand All @@ -40,6 +44,7 @@
friends=['1001'],
appearsIn=[4],
homePlanet=None,
isAlive=False,
)

humanData = {
Expand Down Expand Up @@ -78,6 +83,10 @@ def getCharacter(id):
return humanData.get(id) or droidData.get(id)


def getCharacters(ids):
return map(getCharacter, ids)


def getFriends(character):
return map(getCharacter, character.friends)

Expand All @@ -94,3 +103,9 @@ def getHuman(id):

def getDroid(id):
return droidData.get(id)


def updateHumanAlive(id, status):
human = humanData.get(id)
human = human._replace(isAlive=status)
return human
42 changes: 38 additions & 4 deletions tests/starwars/schema.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
from graphql.type import (GraphQLArgument, GraphQLEnumType, GraphQLEnumValue,
GraphQLField, GraphQLInterfaceType, GraphQLList,
GraphQLNonNull, GraphQLObjectType, GraphQLSchema,
GraphQLString)
GraphQLString, GraphQLBoolean)

from .fixtures import getDroid, getFriends, getHero, getHuman
from .fixtures import getCharacters, getDroid, getFriends, getHero, getHuman, updateHumanAlive

episodeEnum = GraphQLEnumType(
'Episode',
Expand Down Expand Up @@ -72,7 +72,11 @@
'homePlanet': GraphQLField(
GraphQLString,
description='The home planet of the human, or null if unknown.',
)
),
'isAlive': GraphQLField(
GraphQLBoolean,
description='The human is still alive.'
),
},
interfaces=[characterInterface]
)
Expand Down Expand Up @@ -140,7 +144,37 @@
},
resolver=lambda root, info, **args: getDroid(args['id']),
),
'characters': GraphQLField(
GraphQLList(characterInterface),
args={
'ids': GraphQLArgument(
description='list of character ids',
type=GraphQLList(GraphQLString),
)
},
resolver=lambda root, info, **args: getCharacters(args['ids']),
),
}
)

mutationType = GraphQLObjectType(
'Mutation',
fields=lambda: {
'updateHumanAliveStatus': GraphQLField(
humanType,
args={
'id': GraphQLArgument(
description='id of the human',
type=GraphQLNonNull(GraphQLString),
),
'status': GraphQLArgument(
description='set alive status',
type=GraphQLNonNull(GraphQLBoolean),
),
},
resolver=lambda root, info, **args: updateHumanAlive(args['id'], args['status']),
),
}
)

StarWarsSchema = GraphQLSchema(query=queryType, types=[humanType, droidType])
StarWarsSchema = GraphQLSchema(query=queryType, mutation=mutationType, types=[humanType, droidType])
54 changes: 53 additions & 1 deletion tests/starwars/test_dsl.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ def test_invalid_field_on_type_query(ds):
ds.Query.extras.select(
ds.Character.name
)
assert "Field extras doesnt exist in type Query." in str(excInfo.value)
assert "Field extras does not exist in type Query." in str(excInfo.value)


def test_incompatible_query_field(ds):
Expand Down Expand Up @@ -122,3 +122,55 @@ def test_hero_name_query_result(ds):
}
}
assert result == expected


def test_arg_serializer_list(ds):
result = ds.query(
ds.Query.characters.args(ids=[1000, 1001, 1003]).select(
ds.Character.name,
)
)
expected = {
'characters': [
{
'name': 'Luke Skywalker'
},
{
'name': 'Darth Vader'
},
{
'name': 'Leia Organa'
}
]
}
assert result == expected


def test_arg_serializer_enum(ds):
result = ds.query(
ds.Query.hero.args(episode=5).select(
ds.Character.name
)
)
expected = {
'hero': {
'name': 'Luke Skywalker'
}
}
assert result == expected


def test_human_alive_mutation_result(ds):
result = ds.mutate(
ds.Mutation.updateHumanAliveStatus.args(id=1004, status=True).select(
ds.Human.name,
ds.Human.isAlive
)
)
expected = {
'updateHumanAliveStatus': {
'name': 'Wilhuff Tarkin',
'isAlive': True
}
}
assert result == expected
25 changes: 25 additions & 0 deletions tests/test_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -64,3 +64,28 @@ def test_execute_result_error():
with pytest.raises(Exception) as excInfo:
client.execute(query)
assert "Cannot query field \"id\" on type \"Continent\"." in str(excInfo.value)


def test_http_transport_raise_for_status_error():
client = Client(
transport=RequestsHTTPTransport(
url='https://countries.trevorblades.com/',
use_json=False,
headers={
"Content-type": "application/json",
}
)
)

query = gql('''
query getContinents {
continents {
code
name
id
}
}
''')
with pytest.raises(Exception) as excInfo:
client.execute(query)
assert "400 Client Error: Bad Request for url" in str(excInfo.value)
2 changes: 1 addition & 1 deletion tox.ini
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ deps = -e.[test]
; Prevent installing issues: https://github.com/ContinuumIO/anaconda-issues/issues/542
commands =
pip install -U setuptools
pytest --cov=gql tests {posargs}
pytest --cov-report=term-missing --cov=gql tests {posargs}

[testenv:flake8]
basepython = python3.8
Expand Down