Skip to content

Commit

Permalink
Merge e901dc9 into e2f05d3
Browse files Browse the repository at this point in the history
  • Loading branch information
karol-gruszczyk committed Mar 17, 2018
2 parents e2f05d3 + e901dc9 commit e642267
Show file tree
Hide file tree
Showing 30 changed files with 656 additions and 251 deletions.
12 changes: 0 additions & 12 deletions .coveragerc

This file was deleted.

4 changes: 2 additions & 2 deletions .travis.yml
Expand Up @@ -4,8 +4,8 @@ python:
install:
- pip install -r requirements.txt
script:
- pytest --cov
- pytest --cov --cov-config=tox.ini
- flake8 slothql
after_success:
- pip install coveralls
- coveralls
- coveralls --rcfile=tox.ini
2 changes: 2 additions & 0 deletions requirements.txt
@@ -1,4 +1,5 @@
graphql-core>=2<3
cached-property==1.4

# optional
django>=2.0<3
Expand All @@ -12,3 +13,4 @@ pytest-django
flake8
flake8-comprehensions
flake8-commas
flake8-quotes
9 changes: 8 additions & 1 deletion slothql/__init__.py
@@ -1,18 +1,25 @@
from graphql import ResolveInfo

from .types.fields import Field, Integer, Float, String, Boolean, ID, JsonString, DateTime, Date, Time
from .types import Object, Enum, EnumValue
from .types import BaseType, Object, Enum, EnumValue, Union
from .schema import Schema
from .query import gql

__all__ = (
# graphql
'ResolveInfo',

# fields
'Field',
'Integer', 'Float', 'String', 'Boolean', 'ID',
'JsonString',
'DateTime', 'Date', 'Time',

# types
'BaseType',
'Object',
'Enum', 'EnumValue',
'Union',

'Schema',
'gql',
Expand Down
19 changes: 7 additions & 12 deletions slothql/arguments/filters.py
@@ -1,11 +1,9 @@
import operator
import functools
from typing import Iterable, Callable, Dict, Union
from typing import Iterable, Callable, Union, Optional

import graphql
from graphql.language import ast
from slothql.types import scalars

Filter = Callable[[Iterable, ast.Value], Iterable]
FilterValue = Union[int, str, bool, list]


Expand All @@ -32,9 +30,6 @@ def apply(self, collection: Iterable, field_name: str, value: FilterValue) -> It
new_collection = collection
if isinstance(value, dict):
for filter_func, filter_value in value.items():
if filter_func not in self:
raise NotImplementedError(
f'Invalid query function `{filter_func}`. Argument validation not implemented')
new_collection = self[filter_func](new_collection, field_name, filter_value)
else:
new_collection = self[self.default_filter](new_collection, field_name, value)
Expand All @@ -57,11 +52,11 @@ def apply(self, collection: Iterable, field_name: str, value: FilterValue) -> It
}, 'eq')


def get_filter_fields(of_type: graphql.GraphQLScalarType) -> Dict[str, graphql.GraphQLField]:
if of_type == graphql.GraphQLID:
def get_filter_fields(scalar_type: scalars.ScalarType) -> Optional[FilterSet]:
if isinstance(scalar_type, scalars.IDType):
return IDFilterSet
elif of_type == graphql.GraphQLString:
elif isinstance(scalar_type, scalars.StringType):
return StringFilterSet
elif of_type == graphql.GraphQLInt:
elif isinstance(scalar_type, scalars.IntegerType):
return IntegerFilterSet
return {}
raise NotImplementedError()
12 changes: 6 additions & 6 deletions slothql/arguments/tests/integration.py
Expand Up @@ -5,9 +5,9 @@

@pytest.mark.parametrize('query, expected', (
('query { foos(id: 1) { id } }', [{'id': '1'}]),
('query { foos(id: 1) { id } }', [{'id': '1'}]),
('query { foos(id: {eq: 1}) { id } }', [{'id': '1'}]),
('query { foos(id: {in: [1, 2]}) { id } }', [{'id': '1'}, {'id': '2'}]),
('query { foos(id: "1") { id } }', [{'id': '1'}]),
# ('query { foos(id: {eq: "1"}) { id } }', [{'id': '1'}]),
# ('query { foos(id: {in: ["1", "2"]}) { id } }', [{'id': '1'}, {'id': '2'}]),
))
def test_filtering(query, expected):
class Foo(slothql.Object):
Expand All @@ -16,15 +16,15 @@ class Foo(slothql.Object):
class Query(slothql.Object):
foos = slothql.Field(Foo, resolver=lambda: [{'id': '1'}, {'id': '2'}, {'id': '3'}], many=True)

assert {'data': {'foos': expected}} == slothql.gql(slothql.Schema(query=Query), query)
assert {'foos': expected} == slothql.gql(slothql.Schema(query=Query), query).data


@pytest.mark.xfail(reason='Argument validation not implemented')
def test_invalid_filter():
class Foo(slothql.Object):
id = slothql.ID()

class Query(slothql.Object):
foos = slothql.Field(Foo, many=True)

assert {'errors': []} == slothql.gql(slothql.Schema(query=Query), 'query { foos(id: {wtf: 1}) { id } }')
error = {'message': 'Argument "id" has invalid value {wtf: 1}.\nExpected type "ID", found {wtf: 1}.'}
assert [error] == slothql.gql(slothql.Schema(query=Query), 'query { foos(id: {wtf: 1}) { id } }').errors
4 changes: 2 additions & 2 deletions slothql/django/types/model.py
Expand Up @@ -14,8 +14,8 @@
class ModelOptions(ObjectOptions):
__slots__ = 'model',

def __init__(self, attrs: dict):
super().__init__(attrs)
def __init__(self, **kwargs):
super().__init__(**kwargs)
assert self.abstract or self.model, f'"model" is required for object ModelOptions'


Expand Down
57 changes: 34 additions & 23 deletions slothql/query.py
@@ -1,33 +1,48 @@
from typing import List, Tuple, Any, Optional

from graphql import Source, parse, validate, GraphQLSchema
from graphql.error import format_error, GraphQLSyntaxError
from graphql.execution import execute, ExecutionResult
from graphql import execute

import slothql


class QueryExecutor:
def __init__(self, schema: GraphQLSchema) -> None:
self.schema = schema
assert isinstance(schema, GraphQLSchema), f'schema has to be of type GraphQLSchema, not {self.schema}'
class ExecutionResult(dict):
@property
def invalid(self) -> bool:
return bool(self.get('errors'))

@property
def errors(self) -> Optional[list]:
return self.get('errors', [])

@property
def data(self):
return self.get('data')


def query(self, query: str) -> dict:
class Query:
__slots__ = 'query', 'schema', 'ast', 'errors'

def __init__(self, query: str, schema: slothql.Schema):
assert isinstance(query, str), f'Expected query string, got {query}'
assert isinstance(schema, GraphQLSchema), f'schema has to be of type Schema, not {schema}'

result = self.execute_query(query)
if result.invalid:
return {'errors': [self.format_error(error) for error in result.errors]}
return {'data': result.data}
self.query, self.schema = query, schema
self.ast, self.errors = self.get_ast(self.query, self.schema)

def execute_query(self, query: str) -> ExecutionResult:
@classmethod
def get_ast(cls, query, schema) -> Tuple[Any, List[GraphQLSyntaxError]]:
try:
ast = get_ast(query)
ast = parse(Source(query))
except GraphQLSyntaxError as e:
return ExecutionResult(errors=[e], invalid=True)
return None, [e]
return ast, validate(schema, ast)

validation_errors = validate(self.schema, ast)
if validation_errors:
return ExecutionResult(errors=validation_errors, invalid=True)
return execute(self.schema, ast)
def execute(self) -> ExecutionResult:
if self.errors:
return ExecutionResult(errors=[self.format_error(e) for e in self.errors])
return ExecutionResult(data=execute(self.schema, self.ast).data)

@classmethod
def format_error(cls, error: Exception) -> dict:
Expand All @@ -36,9 +51,5 @@ def format_error(cls, error: Exception) -> dict:
return {'message': str(error)}


def get_ast(query: str):
return parse(Source(query))


def gql(schema: slothql.Schema, query: str) -> dict:
return QueryExecutor(schema).query(query)
def gql(schema: slothql.Schema, query: str) -> ExecutionResult:
return Query(query, schema).execute()

0 comments on commit e642267

Please sign in to comment.