Skip to content

Commit 2ff37cb

Browse files
authored
Merge branch 'master' into py36_support
2 parents 24973df + 4aecd32 commit 2ff37cb

38 files changed

+940
-369
lines changed

.pre-commit-config.yaml

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
- repo: git://github.com/pre-commit/pre-commit-hooks
2+
sha: v0.8.0
3+
hooks:
4+
- id: autopep8-wrapper
5+
args:
6+
- -i
7+
- --ignore=E128,E309,E501
8+
exclude: ^docs/.*$
9+
- id: check-json
10+
- id: check-yaml
11+
- id: debug-statements
12+
- id: end-of-file-fixer
13+
exclude: ^docs/.*$
14+
- id: trailing-whitespace
15+
- id: pretty-format-json
16+
args:
17+
- --autofix
18+
- --indent=4

README.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,13 +17,13 @@ For questions, ask [Stack Overflow](http://stackoverflow.com/questions/tagged/gr
1717

1818
## Getting Started
1919

20-
An overview of the GraphQL language is available in the
20+
An overview of the GraphQL language is available in the
2121
[README](https://github.com/facebook/graphql/blob/master/README.md) for the
22-
[Specification for GraphQL](https://github.com/facebook/graphql).
22+
[Specification for GraphQL](https://github.com/facebook/graphql).
2323

2424
The overview describes a simple set of GraphQL examples that exist as [tests](https://github.com/graphql-python/graphql-core/tree/master/tests/)
2525
in this repository. A good way to get started is to walk through that README and the corresponding tests
26-
in parallel.
26+
in parallel.
2727

2828
### Using graphql-core
2929

graphql/__init__.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -193,6 +193,16 @@
193193
Undefined,
194194
)
195195

196+
# Utilities for dynamic execution engines
197+
from .backend import (
198+
GraphQLBackend,
199+
GraphQLDocument,
200+
GraphQLCoreBackend,
201+
GraphQLDeciderBackend,
202+
GraphQLCachedBackend,
203+
get_default_backend,
204+
set_default_backend,
205+
)
196206

197207
VERSION = (2, 0, 1, 'final', 0)
198208
__version__ = get_version(VERSION)
@@ -282,4 +292,11 @@
282292
'value_from_ast',
283293
'get_version',
284294
'Undefined',
295+
'GraphQLBackend',
296+
'GraphQLDocument',
297+
'GraphQLCoreBackend',
298+
'GraphQLDeciderBackend',
299+
'GraphQLCachedBackend',
300+
'get_default_backend',
301+
'set_default_backend',
285302
)

graphql/backend/__init__.py

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
# -*- coding: utf-8 -*-
2+
"""
3+
This module provides a dynamic way of using different
4+
engines for a GraphQL schema query resolution.
5+
"""
6+
7+
from .base import GraphQLBackend, GraphQLDocument
8+
from .core import GraphQLCoreBackend
9+
from .decider import GraphQLDeciderBackend
10+
from .cache import GraphQLCachedBackend
11+
12+
_default_backend = None
13+
14+
15+
def get_default_backend():
16+
global _default_backend
17+
if _default_backend is None:
18+
_default_backend = GraphQLCoreBackend()
19+
return _default_backend
20+
21+
22+
def set_default_backend(backend):
23+
global _default_backend
24+
assert isinstance(
25+
backend, GraphQLBackend
26+
), "backend must be an instance of GraphQLBackend."
27+
_default_backend = backend
28+
29+
30+
__all__ = [
31+
"GraphQLBackend",
32+
"GraphQLDocument",
33+
"GraphQLCoreBackend",
34+
"GraphQLDeciderBackend",
35+
"GraphQLCachedBackend",
36+
"get_default_backend",
37+
"set_default_backend",
38+
]

graphql/backend/base.py

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
from ..language import ast
2+
from abc import ABCMeta, abstractmethod
3+
import six
4+
5+
6+
class GraphQLBackend(six.with_metaclass(ABCMeta)):
7+
@abstractmethod
8+
def document_from_string(self, schema, request_string):
9+
raise NotImplementedError(
10+
"document_from_string method not implemented in {}.".format(self.__class__)
11+
)
12+
13+
14+
class GraphQLDocument(object):
15+
def __init__(self, schema, document_string, document_ast, execute):
16+
self.schema = schema
17+
self.document_string = document_string
18+
self.document_ast = document_ast
19+
self.execute = execute
20+
21+
def get_operations(self):
22+
document_ast = self.document_ast
23+
operations = {}
24+
for definition in document_ast.definitions:
25+
if isinstance(definition, ast.OperationDefinition):
26+
if definition.name:
27+
operation_name = definition.name.value
28+
else:
29+
operation_name = None
30+
operations[operation_name] = definition.operation
31+
return operations

graphql/backend/cache.py

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
from hashlib import sha1
2+
from six import string_types
3+
from ..type import GraphQLSchema
4+
5+
from .base import GraphQLBackend
6+
7+
_cached_schemas = {}
8+
9+
_cached_queries = {}
10+
11+
12+
def get_unique_schema_id(schema):
13+
"""Get a unique id given a GraphQLSchema"""
14+
assert isinstance(schema, GraphQLSchema), (
15+
"Must receive a GraphQLSchema as schema. Received {}"
16+
).format(repr(schema))
17+
18+
if schema not in _cached_schemas:
19+
_cached_schemas[schema] = sha1(str(schema).encode("utf-8")).hexdigest()
20+
return _cached_schemas[schema]
21+
22+
23+
def get_unique_document_id(query_str):
24+
"""Get a unique id given a query_string"""
25+
assert isinstance(query_str, string_types), (
26+
"Must receive a string as query_str. Received {}"
27+
).format(repr(query_str))
28+
29+
if query_str not in _cached_queries:
30+
_cached_queries[query_str] = sha1(str(query_str).encode("utf-8")).hexdigest()
31+
return _cached_queries[query_str]
32+
33+
34+
class GraphQLCachedBackend(GraphQLBackend):
35+
def __init__(self, backend, cache_map=None, use_consistent_hash=False):
36+
assert isinstance(
37+
backend, GraphQLBackend
38+
), "Provided backend must be an instance of GraphQLBackend"
39+
if cache_map is None:
40+
cache_map = {}
41+
self.backend = backend
42+
self.cache_map = cache_map
43+
self.use_consistent_hash = use_consistent_hash
44+
45+
def get_key_for_schema_and_document_string(self, schema, request_string):
46+
"""This method returns a unique key given a schema and a request_string"""
47+
if self.use_consistent_hash:
48+
schema_id = get_unique_schema_id(schema)
49+
document_id = get_unique_document_id(request_string)
50+
return (schema_id, document_id)
51+
return hash((schema, request_string))
52+
53+
def document_from_string(self, schema, request_string):
54+
"""This method returns a GraphQLQuery (from cache if present)"""
55+
key = self.get_key_for_schema_and_document_string(schema, request_string)
56+
if key not in self.cache_map:
57+
self.cache_map[key] = self.backend.document_from_string(
58+
schema, request_string
59+
)
60+
61+
return self.cache_map[key]

graphql/backend/compiled.py

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
from .base import GraphQLDocument
2+
3+
4+
class GraphQLCompiledDocument(GraphQLDocument):
5+
@classmethod
6+
def from_code(cls, schema, code, uptodate=None, extra_namespace=None):
7+
"""Creates a GraphQLDocument object from compiled code and the globals. This
8+
is used by the loaders and schema to create a document object.
9+
"""
10+
namespace = {"__file__": code.co_filename}
11+
exec(code, namespace)
12+
if extra_namespace:
13+
namespace.update(extra_namespace)
14+
rv = cls._from_namespace(schema, namespace)
15+
rv._uptodate = uptodate
16+
return rv
17+
18+
@classmethod
19+
def from_module_dict(cls, schema, module_dict):
20+
"""Creates a template object from a module. This is used by the
21+
module loader to create a document object.
22+
"""
23+
return cls._from_namespace(schema, module_dict)
24+
25+
@classmethod
26+
def _from_namespace(cls, schema, namespace):
27+
document_string = namespace.get("document_string", "")
28+
document_ast = namespace.get("document_ast")
29+
execute = namespace["execute"]
30+
31+
namespace["schema"] = schema
32+
return cls(
33+
schema=schema,
34+
document_string=document_string,
35+
document_ast=document_ast,
36+
execute=execute,
37+
)

graphql/backend/core.py

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
from functools import partial
2+
from six import string_types
3+
4+
from ..execution import execute, ExecutionResult
5+
from ..language.base import parse, print_ast
6+
from ..language import ast
7+
from ..validation import validate
8+
9+
from .base import GraphQLBackend, GraphQLDocument
10+
11+
12+
def execute_and_validate(schema, document_ast, *args, **kwargs):
13+
do_validation = kwargs.get('validate', True)
14+
if do_validation:
15+
validation_errors = validate(schema, document_ast)
16+
if validation_errors:
17+
return ExecutionResult(
18+
errors=validation_errors,
19+
invalid=True,
20+
)
21+
22+
return execute(schema, document_ast, *args, **kwargs)
23+
24+
25+
class GraphQLCoreBackend(GraphQLBackend):
26+
def __init__(self, executor=None, **kwargs):
27+
super(GraphQLCoreBackend, self).__init__(**kwargs)
28+
self.execute_params = {"executor": executor}
29+
30+
def document_from_string(self, schema, document_string):
31+
if isinstance(document_string, ast.Document):
32+
document_ast = document_string
33+
document_string = print_ast(document_ast)
34+
else:
35+
assert isinstance(document_string, string_types), "The query must be a string"
36+
document_ast = parse(document_string)
37+
return GraphQLDocument(
38+
schema=schema,
39+
document_string=document_string,
40+
document_ast=document_ast,
41+
execute=partial(execute_and_validate, schema, document_ast, **self.execute_params),
42+
)

graphql/backend/decider.py

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
from .base import GraphQLBackend
2+
3+
4+
class GraphQLDeciderBackend(GraphQLBackend):
5+
def __init__(self, backends=None):
6+
if not backends:
7+
raise Exception("Need to provide backends to decide into.")
8+
if not isinstance(backends, (list, tuple)):
9+
raise Exception("Provided backends need to be a list or tuple.")
10+
self.backends = backends
11+
super(GraphQLDeciderBackend, self).__init__()
12+
13+
def document_from_string(self, schema, request_string):
14+
for backend in self.backends:
15+
try:
16+
return backend.document_from_string(schema, request_string)
17+
except Exception:
18+
continue
19+
20+
raise Exception(
21+
"GraphQLDeciderBackend was not able to retrieve a document. Backends tried: {}".format(
22+
repr(self.backends)
23+
)
24+
)

0 commit comments

Comments
 (0)