diff --git a/graphql_env/__init__.py b/graphql_env/__init__.py index 2cf1308..f74a574 100644 --- a/graphql_env/__init__.py +++ b/graphql_env/__init__.py @@ -2,23 +2,27 @@ """Top-level package for GraphQL Environment.""" __author__ = """Syrus Akbary""" -__email__ = 'me@syrusakbary.com' -__version__ = '0.1.0' +__email__ = "me@syrusakbary.com" +__version__ = "0.1.0" -from .environment import GraphQLEnvironment -from .backend import (GraphQLBackend, GraphQLDocument, GraphQLCoreBackend, - GraphQLDeciderBackend, get_default_backend, - set_default_backend) - -GraphQLEnv = GraphQLEnvironment +from .backend import ( + GraphQLBackend, + GraphQLDocument, + GraphQLCoreBackend, + GraphQLDeciderBackend, + GraphQLCachedBackend, + get_default_backend, + set_default_backend, +) +from .loader import GraphQLLoader __all__ = [ - 'GraphQLEnv', - 'GraphQLEnvironment', - 'GraphQLBackend', - 'GraphQLDocument', - 'GraphQLCoreBackend', - 'GraphQLDeciderBackend', - 'get_default_backend', - 'set_default_backend', + "GraphQLBackend", + "GraphQLDocument", + "GraphQLCachedBackend", + "GraphQLCoreBackend", + "GraphQLDeciderBackend", + "get_default_backend", + "set_default_backend", + "GraphQLLoader", ] diff --git a/graphql_env/_compat.py b/graphql_env/_compat.py index 696d711..e6dfe2d 100644 --- a/graphql_env/_compat.py +++ b/graphql_env/_compat.py @@ -11,15 +11,15 @@ import sys PY2 = sys.version_info[0] == 2 -PYPY = hasattr(sys, 'pypy_translation_info') +PYPY = hasattr(sys, "pypy_translation_info") _identity = lambda x: x if not PY2: unichr = chr range_type = range text_type = str - string_types = (str, ) - integer_types = (int, ) + string_types = (str,) + integer_types = (int,) iterkeys = lambda d: iter(d.keys()) itervalues = lambda d: iter(d.values()) @@ -27,6 +27,7 @@ import pickle from io import BytesIO, StringIO + NativeStringIO = StringIO def reraise(tp, value, tb=None): @@ -56,11 +57,13 @@ def reraise(tp, value, tb=None): import cPickle as pickle from cStringIO import StringIO as BytesIO, StringIO + NativeStringIO = BytesIO - exec ('def reraise(tp, value, tb=None):\n raise tp, value, tb') + exec("def reraise(tp, value, tb=None):\n raise tp, value, tb") from itertools import imap, izip, ifilter + intern = intern def implements_iterator(cls): @@ -70,12 +73,12 @@ def implements_iterator(cls): def implements_to_string(cls): cls.__unicode__ = cls.__str__ - cls.__str__ = lambda x: x.__unicode__().encode('utf-8') + cls.__str__ = lambda x: x.__unicode__().encode("utf-8") return cls def encode_filename(filename): if isinstance(filename, unicode): - return filename.encode('utf-8') + return filename.encode("utf-8") return filename @@ -89,7 +92,7 @@ class metaclass(type): def __new__(cls, name, this_bases, d): return meta(name, bases, d) - return type.__new__(metaclass, 'temporary_class', (), {}) + return type.__new__(metaclass, "temporary_class", (), {}) try: diff --git a/graphql_env/backend/__init__.py b/graphql_env/backend/__init__.py index 6283880..9393260 100644 --- a/graphql_env/backend/__init__.py +++ b/graphql_env/backend/__init__.py @@ -1,27 +1,32 @@ from .base import GraphQLBackend, GraphQLDocument from .core import GraphQLCoreBackend from .decider import GraphQLDeciderBackend +from .cache import GraphQLCachedBackend -_default_backend = GraphQLCoreBackend() +_default_backend = None def get_default_backend(): + global _default_backend + if _default_backend is None: + _default_backend = GraphQLCoreBackend() return _default_backend def set_default_backend(backend): global _default_backend assert isinstance( - backend, - GraphQLBackend), "backend must be an instance of GraphQLBackend." + backend, GraphQLBackend + ), "backend must be an instance of GraphQLBackend." _default_backend = backend __all__ = [ - 'GraphQLBackend', - 'GraphQLDocument', - 'GraphQLCoreBackend', - 'GraphQLDeciderBackend', - 'get_default_backend', - 'set_default_backend', + "GraphQLBackend", + "GraphQLDocument", + "GraphQLCoreBackend", + "GraphQLDeciderBackend", + "GraphQLCachedBackend", + "get_default_backend", + "set_default_backend", ] diff --git a/graphql_env/backend/base.py b/graphql_env/backend/base.py index 62902e4..da8e834 100644 --- a/graphql_env/backend/base.py +++ b/graphql_env/backend/base.py @@ -1,25 +1,11 @@ class GraphQLBackend(object): - def __init__(self, cache=None, use_cache=True): - if cache is None and use_cache: - cache = {} - self._cache = cache - - def document_from_cache_or_string(self, schema, request_string, key): - '''This method should return a GraphQLQuery''' - if not key or self._cache is None: - # We return without caching - return self.document_from_string(schema, request_string) - - if key not in self._cache: - self._cache[key] = self.document_from_string( - schema, request_string) - - return self._cache[key] + def __init__(self): + pass def document_from_string(self, schema, request_string): raise NotImplementedError( - "document_from_string method not implemented in {}.".format( - self.__class__)) + "document_from_string method not implemented in {}.".format(self.__class__) + ) class GraphQLDocument(object): @@ -41,4 +27,5 @@ class GraphQLDocument(object): def execute(self, *args, **kwargs): raise NotImplementedError( - "execute method not implemented in {}.".format(self.__class__)) + "execute method not implemented in {}.".format(self.__class__) + ) diff --git a/graphql_env/backend/cache.py b/graphql_env/backend/cache.py new file mode 100644 index 0000000..1d7703a --- /dev/null +++ b/graphql_env/backend/cache.py @@ -0,0 +1,31 @@ +from .base import GraphQLBackend + +# from .utils import get_unique_document_id, get_unique_schema_id + + +class GraphQLCachedBackend(GraphQLBackend): + def __init__(self, backend, cache_map=None): + assert isinstance( + backend, GraphQLBackend + ), "Provided backend must be an instance of GraphQLBackend" + if cache_map is None: + cache_map = {} + self.backend = backend + self.cache_map = cache_map + + def get_key_for_schema_and_document_string(self, schema, request_string): + """This method returns a unique key given a schema and a request_string""" + return hash((schema, request_string)) + # schema_id = get_unique_schema_id(schema) + # document_id = get_unique_document_id(request_string) + # return (schema_id, document_id) + + def document_from_string(self, schema, request_string): + """This method returns a GraphQLQuery (from cache if present)""" + key = self.get_key_for_schema_and_document_string(schema, request_string) + if key not in self.cache_map: + self.cache_map[key] = self.backend.document_from_string( + schema, request_string + ) + + return self.cache_map[key] diff --git a/graphql_env/backend/compiled.py b/graphql_env/backend/compiled.py index a2b3fd0..3bf3b46 100644 --- a/graphql_env/backend/compiled.py +++ b/graphql_env/backend/compiled.py @@ -4,8 +4,8 @@ def from_code(cls, schema, code, uptodate=None, extra_namespace=None): """Creates a GraphQLQuery object from compiled code and the globals. This is used by the loaders and schema to create a template object. """ - namespace = {'__file__': code.co_filename} - exec (code, namespace) + namespace = {"__file__": code.co_filename} + exec(code, namespace) if extra_namespace: namespace.update(extra_namespace) rv = cls._from_namespace(schema, namespace) @@ -23,14 +23,14 @@ def from_module_dict(cls, schema, module_dict): def _from_namespace(cls, schema, namespace): t = object.__new__(cls) t.schema = schema - t.execute_func = namespace['execute'] + t.execute_func = namespace["execute"] t._module = None t._uptodate = None # store the reference - namespace['schema'] = schema - namespace['__graphql_query__'] = t + namespace["schema"] = schema + namespace["__graphql_query__"] = t return t def execute(self, *args, **kwargs): - return self.execute_func(*args, **kwargs) \ No newline at end of file + return self.execute_func(*args, **kwargs) diff --git a/graphql_env/backend/core.py b/graphql_env/backend/core.py index 3f78650..6863d60 100644 --- a/graphql_env/backend/core.py +++ b/graphql_env/backend/core.py @@ -8,7 +8,7 @@ class GraphQLCoreBackend(GraphQLBackend): def __init__(self, executor=None, **kwargs): super(GraphQLCoreBackend, self).__init__(**kwargs) - self.execute_params = {'executor': executor} + self.execute_params = {"executor": executor} def document_from_string(self, schema, request_string): return GraphQLCoreDocument(schema, request_string, self.execute_params) @@ -17,7 +17,11 @@ def document_from_string(self, schema, request_string): def get_operation_from_operation_name(document_ast, operation_name): for definition in document_ast.definitions: if isinstance(definition, ast.OperationDefinition): - if not operation_name or definition.name and definition.name.value == operation_name: + if ( + not operation_name + or definition.name + and definition.name.value == operation_name + ): return definition.operation return None @@ -38,22 +42,26 @@ def __init__(self, schema, request_string, execute_params=None): self.document_ast = None self.errors = [e] - def execute(self, - root=None, - context=None, - middleware=None, - operation_name=None, - variables=None, - allowed_operations=None): + def execute( + self, + root=None, + context=None, + middleware=None, + operation_name=None, + variables=None, + allowed_operations=None, + ): if self.errors: return ExecutionResult(errors=self.errors, invalid=True) try: if allowed_operations is not None: operation_type = get_operation_from_operation_name( - self.document_ast, operation_name) + self.document_ast, operation_name + ) if operation_type and operation_type not in allowed_operations: - raise GraphQLError("{} operations are not allowed.".format( - operation_type)) + raise GraphQLError( + "{} operations are not allowed.".format(operation_type) + ) return execute( self.schema, self.document_ast, @@ -62,6 +70,7 @@ def execute(self, context_value=context, variable_values=variables or {}, operation_name=operation_name, - **self.execute_params) + **self.execute_params + ) except Exception as e: return ExecutionResult(errors=[e]) diff --git a/graphql_env/backend/decider.py b/graphql_env/backend/decider.py index f1f7cae..20c67dc 100644 --- a/graphql_env/backend/decider.py +++ b/graphql_env/backend/decider.py @@ -2,40 +2,23 @@ class GraphQLDeciderBackend(GraphQLBackend): - def __init__(self, backends=None, cache=None, use_cache=None): + def __init__(self, backends=None): if not backends: raise Exception("Need to provide backends to decide into.") if not isinstance(backends, (list, tuple)): raise Exception("Provided backends need to be a list or tuple.") - if cache is None and use_cache is None: - use_cache = False self.backends = backends - super(GraphQLDeciderBackend, self).__init__( - cache=cache, use_cache=use_cache) - - def document_from_cache_or_string(self, schema, request_string, key): - if self._cache and key in self._cache: - return self._cache[key] + super(GraphQLDeciderBackend, self).__init__() + def document_from_string(self, schema, request_string): for backend in self.backends: try: - document = backend.document_from_cache_or_string( - schema, request_string, key) - # If no error has been raised, we are ok :) - if self._cache is not None: - self._cache[key] = document - return document - except Exception: + return backend.document_from_string(schema, request_string) + except Exception as e: continue raise Exception( - "GraphQLDeciderBackend was not able to retrieve a document. Backends tried: {}". - format(repr(self.backends))) - - # def document_from_string(self, schema, request_string): - # for backend in self.backends: - # try: - # return backend.document_from_string(schema, request_string) - # except - # continue - # raise Exception("GraphQLDeciderBackend was not able to retrieve a document. Backends tried: {}".format(repr(self.backends))) + "GraphQLDeciderBackend was not able to retrieve a document. Backends tried: {}".format( + repr(self.backends) + ) + ) diff --git a/graphql_env/backend/quiver_cloud.py b/graphql_env/backend/quiver_cloud.py index 61d3b30..3cfe25d 100644 --- a/graphql_env/backend/quiver_cloud.py +++ b/graphql_env/backend/quiver_cloud.py @@ -5,7 +5,7 @@ from .._compat import urlparse -GRAPHQL_QUERY = ''' +GRAPHQL_QUERY = """ mutation($schemaDsl: String!, $query: String!) { generateCode( schemaDsl: $schemaDsl @@ -22,7 +22,7 @@ } } } -''' +""" class GraphQLQuiverCloudBackend(GraphQLBackend): @@ -35,58 +35,57 @@ def __init__(self, dsn, python_options=None, **options): netloc = url.hostname if url.port: - netloc += ':%s' % url.port + netloc += ":%s" % url.port - path_bits = url.path.rsplit('/', 1) + path_bits = url.path.rsplit("/", 1) if len(path_bits) > 1: path = path_bits[0] else: - path = '' + path = "" - self.api_url = '%s://%s%s' % (url.scheme.rsplit('+', 1)[-1], netloc, - path) + self.api_url = "%s://%s%s" % (url.scheme.rsplit("+", 1)[-1], netloc, path) self.public_key = url.username self.secret_key = url.password self.extra_namespace = {} if python_options is None: python_options = {} - wait_for_promises = python_options.pop('wait_for_promises', None) + wait_for_promises = python_options.pop("wait_for_promises", None) if wait_for_promises: - assert callable( - wait_for_promises), "wait_for_promises must be callable." - self.extra_namespace['wait_for_promises'] = wait_for_promises + assert callable(wait_for_promises), "wait_for_promises must be callable." + self.extra_namespace["wait_for_promises"] = wait_for_promises self.python_options = python_options def make_post_request(self, url, auth, json_payload): - '''This function executes the request with the provided - json payload and return the json response''' + """This function executes the request with the provided + json payload and return the json response""" import requests + response = requests.post(url, auth=auth, json=json_payload) return response.json() def generate_source(self, schema, query): - variables = {'schemaDsl': print_schema(schema), 'query': query} + variables = {"schemaDsl": print_schema(schema), "query": query} json_response = self.make_post_request( "{}/graphql".format(self.api_url), auth=(self.public_key, self.secret_key), - json_payload={'query': GRAPHQL_QUERY, - 'variables': variables}) + json_payload={"query": GRAPHQL_QUERY, "variables": variables}, + ) - data = json_response.get('data', {}) - code_generation = data.get('generateCode', {}) - code = code_generation.get('code') + data = json_response.get("data", {}) + code_generation = data.get("generateCode", {}) + code = code_generation.get("code") if not code: - raise Exception( - "Cant get the code. Received json from Quiver Cloud") + raise Exception("Cant get the code. Received json from Quiver Cloud") code = str(code) return code def document_from_string(self, schema, request_string): source = self.generate_source(schema, request_string) - filename = '' - code = compile(source, filename, 'exec') + filename = "" + code = compile(source, filename, "exec") uptodate = lambda: True - document = GraphQLCompiledDocument.from_code(schema, code, uptodate, - self.extra_namespace) + document = GraphQLCompiledDocument.from_code( + schema, code, uptodate, self.extra_namespace + ) return document diff --git a/graphql_env/backend/tests/__init__.py b/graphql_env/backend/tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/schema.py b/graphql_env/backend/tests/schema.py similarity index 100% rename from tests/schema.py rename to graphql_env/backend/tests/schema.py diff --git a/graphql_env/backend/tests/test_cache.py b/graphql_env/backend/tests/test_cache.py new file mode 100644 index 0000000..40c1afb --- /dev/null +++ b/graphql_env/backend/tests/test_cache.py @@ -0,0 +1,16 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +"""Tests for `graphql_env` package.""" + +import pytest + +from graphql_env import GraphQLCoreBackend, GraphQLCachedBackend +from graphql.execution.executors.sync import SyncExecutor +from .schema import schema + + +def test_backend_is_cached_when_needed(): + cached_backend = GraphQLCachedBackend(GraphQLCoreBackend()) + document1 = cached_backend.document_from_string(schema, "{ hello }") + document2 = cached_backend.document_from_string(schema, "{ hello }") + assert document1 == document2 diff --git a/graphql_env/backend/tests/test_core.py b/graphql_env/backend/tests/test_core.py new file mode 100644 index 0000000..542f90f --- /dev/null +++ b/graphql_env/backend/tests/test_core.py @@ -0,0 +1,47 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +"""Tests for `graphql_env` package.""" + +import pytest +from graphql.execution.executors.sync import SyncExecutor + +from ..base import GraphQLBackend, GraphQLDocument +from ..core import GraphQLCoreBackend +from .schema import schema + + +def test_core_backend(): + """Sample pytest test function with the pytest fixture as an argument.""" + backend = GraphQLCoreBackend() + assert isinstance(backend, GraphQLBackend) + document = backend.document_from_string(schema, "{ hello }") + assert isinstance(document, GraphQLDocument) + result = document.execute() + assert not result.errors + assert result.data == {"hello": "World"} + + +def test_backend_is_not_cached_by_default(): + """Sample pytest test function with the pytest fixture as an argument.""" + backend = GraphQLCoreBackend() + document1 = backend.document_from_string(schema, "{ hello }") + document2 = backend.document_from_string(schema, "{ hello }") + assert document1 != document2 + + +class BaseExecutor(SyncExecutor): + executed = False + + def execute(self, *args, **kwargs): + self.executed = True + return super(BaseExecutor, self).execute(*args, **kwargs) + + +def test_backend_can_execute_custom_executor(): + executor = BaseExecutor() + backend = GraphQLCoreBackend(executor=executor) + document1 = backend.document_from_string(schema, "{ hello }") + result = document1.execute() + assert not result.errors + assert result.data == {"hello": "World"} + assert executor.executed diff --git a/graphql_env/backend/tests/test_decider.py b/graphql_env/backend/tests/test_decider.py new file mode 100644 index 0000000..e19dfdf --- /dev/null +++ b/graphql_env/backend/tests/test_decider.py @@ -0,0 +1,77 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +"""Tests for `graphql_env` package.""" + +import pytest + +from ..base import GraphQLBackend, GraphQLDocument +from ..core import GraphQLCoreBackend +from ..cache import GraphQLCachedBackend +from ..decider import GraphQLDeciderBackend + +from .schema import schema + + +class FakeBackend(GraphQLBackend): + reached = False + + def __init__(self, raises=False): + self.raises = raises + + def document_from_string(self, *args, **kwargs): + self.reached = True + if self.raises: + raise Exception("Backend failed") + + def reset(self): + self.reached = False + + +def test_decider_backend_healthy_backend(): + backend1 = FakeBackend() + backend2 = FakeBackend() + decider_backend = GraphQLDeciderBackend([backend1, backend2]) + + decider_backend.document_from_string(schema, "{ hello }") + assert backend1.reached + assert not backend2.reached + + +def test_decider_backend_unhealthy_backend(): + backend1 = FakeBackend(raises=True) + backend2 = FakeBackend() + decider_backend = GraphQLDeciderBackend([backend1, backend2]) + + decider_backend.document_from_string(schema, "{ hello }") + assert backend1.reached + assert backend2.reached + + +def test_decider_backend_dont_use_cache(): + backend1 = FakeBackend() + backend2 = FakeBackend() + decider_backend = GraphQLDeciderBackend([backend1, backend2]) + + decider_backend.document_from_string(schema, "{ hello }") + assert backend1.reached + assert not backend2.reached + + backend1.reset() + decider_backend.document_from_string(schema, "{ hello }") + assert backend1.reached + + +def test_decider_backend_use_cache_if_provided(): + backend1 = FakeBackend() + backend2 = FakeBackend() + decider_backend = GraphQLDeciderBackend( + [GraphQLCachedBackend(backend1), GraphQLCachedBackend(backend2)] + ) + + decider_backend.document_from_string(schema, "{ hello }") + assert backend1.reached + assert not backend2.reached + + backend1.reset() + decider_backend.document_from_string(schema, "{ hello }") + assert not backend1.reached diff --git a/graphql_env/utils.py b/graphql_env/backend/utils.py similarity index 69% rename from graphql_env/utils.py rename to graphql_env/backend/utils.py index 7d8a58f..8999167 100644 --- a/graphql_env/utils.py +++ b/graphql_env/backend/utils.py @@ -10,22 +10,22 @@ def get_unique_schema_id(schema): - '''Get a unique id given a GraphQLSchema''' + """Get a unique id given a GraphQLSchema""" assert isinstance(schema, GraphQLSchema), ( "Must receive a GraphQLSchema as schema. Received {}" ).format(repr(schema)) if schema not in _schemas: - _schemas[schema] = sha1(str(schema).encode('utf-8')).hexdigest() + _schemas[schema] = sha1(str(schema).encode("utf-8")).hexdigest() return _schemas[schema] -def get_unique_query_id(query_str): - '''Get a unique id given a query_string''' +def get_unique_document_id(query_str): + """Get a unique id given a query_string""" assert isinstance(query_str, string_types), ( "Must receive a string as query_str. Received {}" ).format(repr(query_str)) if query_str not in _queries: - _queries[query_str] = sha1(str(query_str).encode('utf-8')).hexdigest() + _queries[query_str] = sha1(str(query_str).encode("utf-8")).hexdigest() return _queries[query_str] diff --git a/graphql_env/environment.py b/graphql_env/environment.py deleted file mode 100644 index d3cf7ad..0000000 --- a/graphql_env/environment.py +++ /dev/null @@ -1,71 +0,0 @@ -from .backend import GraphQLBackend, get_default_backend, GraphQLDocument -from .utils import get_unique_query_id, get_unique_schema_id -from ._compat import string_types -from .params import GraphQLParams - - -class GraphQLEnvironment(object): - def __init__(self, schema, middleware=None, backend=None, store=None): - self.schema = schema - self.middleware = middleware - if backend is None: - backend = get_default_backend() - else: - assert isinstance( - backend, - GraphQLBackend), "backend must be instance of GraphQLBackend" - self.backend = backend - self.store = store - self.schema_key = get_unique_schema_id(schema) - - def document_from_string(self, source): - """Load a document from a string. This parses the source given and - returns a :class:`GraphQLDocument` object. - """ - key = (self.schema_key, get_unique_query_id(source)) - document = self.backend.document_from_cache_or_string( - self.schema, source, key=key) - return document - - def load_document(self, query_id): - """ - Load a document given a query_id - """ - if not self.store: - raise Exception( - "The GraphQL Environment doesn't have set any store.") - - document = self.store[query_id] - - if isinstance(document, string_types): - return self.document_from_string(document) - elif isinstance(document, GraphQLDocument): - return document - - raise Exception( - "Document returned from the store must be an string or a GraphQLDocument. Received {}.". - format(repr(document))) - - def get_document_from_params(self, params): - if params.query_id: - return self.load_document(params.query_id) - return self.document_from_string(params.query) - - def __call__(self, - graphql_params, - root=None, - context=None, - middleware=None, - allowed_operations=None): - assert isinstance( - graphql_params, GraphQLParams - ), "GraphQL params must be an instance of GraphQLParams." - - document = self.get_document_from_params(graphql_params) - return document.execute( - root=root, - context=context, - middleware=middleware, - operation_name=graphql_params.operation_name, - variables=graphql_params.variables, - allowed_operations=allowed_operations) diff --git a/graphql_env/loader.py b/graphql_env/loader.py new file mode 100644 index 0000000..f101584 --- /dev/null +++ b/graphql_env/loader.py @@ -0,0 +1,50 @@ +from .backend import GraphQLBackend, get_default_backend, GraphQLDocument +from ._compat import string_types +from .params import GraphQLParams + + +class GraphQLLoader(object): + """GraphQLLoader is a GraphQL document loader. + It returns a GraphQL document given params (query or document id) + """ + + def __init__(self, backend=None, store=None): + if backend is None: + backend = get_default_backend() + else: + assert isinstance( + backend, GraphQLBackend + ), "backend must be instance of GraphQLBackend" + self.backend = backend + self.store = store + + def document_from_string(self, schema, source): + """Load a document from a string. This parses the source given and + returns a :class:`GraphQLDocument` object. + """ + return self.backend.document_from_string(schema, source) + + def load_document(self, schema, document_id): + """ + Load a document given a document_id + """ + if not self.store: + raise Exception("GraphQLLoader doesn't have set any store.") + + document = self.store.load(schema, document_id) + + if isinstance(document, string_types): + return self.document_from_string(schema, document) + elif isinstance(document, GraphQLDocument): + return document + + raise Exception( + "Document returned from the store must be an string or a GraphQLDocument. Received {}.".format( + repr(document) + ) + ) + + def get_document_from_params(self, schema, params): + if params.document_id: + return self.load_document(schema, params.document_id) + return self.document_from_string(schema, params.query) diff --git a/graphql_env/params.py b/graphql_env/params.py index 293e303..35f48fa 100644 --- a/graphql_env/params.py +++ b/graphql_env/params.py @@ -4,24 +4,24 @@ class GraphQLParams(object): - '''The parameters that usually came from the side of the client''' + """The parameters that usually came from the side of the client""" - __slots__ = ('query', 'query_id', 'operation_name', 'variables') + __slots__ = ("query", "document_id", "operation_name", "variables") - def __init__(self, - query=None, - query_id=None, - operation_name=None, - variables=None): - if not query or query_id: + def __init__( + self, query=None, document_id=None, operation_name=None, variables=None + ): + if not query and not document_id: raise GraphQLError("Must provide query string.") if variables and not isinstance(variables, Mapping): raise GraphQLError( - "variables, if provided need to be a mapping. Received {}.". - format(repr(variables))) + "variables, if provided need to be a mapping. Received {}.".format( + repr(variables) + ) + ) self.query = query - self.query_id = query_id + self.document_id = document_id self.operation_name = operation_name self.variables = variables or {} diff --git a/graphql_env/server/base.py b/graphql_env/server/base.py new file mode 100644 index 0000000..60536f8 --- /dev/null +++ b/graphql_env/server/base.py @@ -0,0 +1,56 @@ +from graphql import GraphQLSchema +from graphql_env.loader import GraphQLLoader + +from .utils import format_error as default_format_error + + +class GraphQLBase(object): + schema = None + executor = None + root = None + graphiql = False + graphiql_version = None + graphiql_template = None + graphiql_html_title = None + format_error = None + context = None + middleware = None + loader = None + + def __init__( + self, + schema=None, + executor=None, + root=None, + root_value=None, + graphiql=False, + graphiql_version=None, + graphiql_template=None, + graphiql_html_title=None, + format_error=None, + context=None, + middleware=None, + loader=None, + batch=False, + **kwargs + ): + if schema: + assert isinstance( + schema, GraphQLSchema + ), "A Schema is required to be provided to GraphQLView." + if batch: + raise Exception("GraphQLView batch is no longer supported.") + + self.schema = schema + self.executor = executor + self.root = root or root_value + self.graphiql = graphiql + self.graphiql_version = graphiql_version + self.graphiql_template = graphiql_template + self.graphiql_html_title = graphiql_html_title + self.format_error = format_error or default_format_error + self.context = context + self.middleware = middleware + self.loader = loader or GraphQLLoader() + + super(GraphQLBase, self).__init__(**kwargs) diff --git a/graphql_env/server/flask/exceptions.py b/graphql_env/server/exceptions.py similarity index 66% rename from graphql_env/server/flask/exceptions.py rename to graphql_env/server/exceptions.py index 6d47eb1..4392779 100644 --- a/graphql_env/server/flask/exceptions.py +++ b/graphql_env/server/exceptions.py @@ -10,17 +10,18 @@ def __init__(self, detail=None): class InvalidJSONError(GraphQLHTTPError): status_code = 400 - default_detail = 'POST body sent invalid JSON.' + default_detail = "POST body sent invalid JSON." class InvalidVariablesJSONError(GraphQLHTTPError): status_code = 400 - default_detail = 'Variables are invalid JSON.' + default_detail = "Variables are invalid JSON." class HTTPMethodNotAllowed(GraphQLHTTPError): - default_detail = 'GraphQL only supports GET and POST requests.' + status_code = 405 + default_detail = "GraphQL only supports GET and POST requests." class MissingQueryError(GraphQLHTTPError): - default_detail = 'Must provide query string.' + default_detail = "Must provide query string." diff --git a/graphql_env/server/flask/__init__.py b/graphql_env/server/flask/__init__.py index d657f90..ad5e48b 100644 --- a/graphql_env/server/flask/__init__.py +++ b/graphql_env/server/flask/__init__.py @@ -1 +1,3 @@ -from .graphql_view import graphql_view +from .graphql_view import GraphQLView + +__all__ = ["GraphQLView"] diff --git a/graphql_env/server/flask/graphiql.py b/graphql_env/server/flask/graphiql.py index c115b38..a7c26d2 100644 --- a/graphql_env/server/flask/graphiql.py +++ b/graphql_env/server/flask/graphiql.py @@ -1,8 +1,8 @@ from flask import render_template_string -GRAPHIQL_VERSION = '0.7.1' +GRAPHIQL_VERSION = "0.7.1" -TEMPLATE = '''