From 64c7d3562f1b18643824beffff5525099753f1ff Mon Sep 17 00:00:00 2001 From: Syrus Akbary Date: Tue, 8 May 2018 11:43:11 +0900 Subject: [PATCH 01/20] First iteration of a class-based view for GraphQL env flask --- graphql_env/environment.py | 17 +- graphql_env/params.py | 8 +- graphql_env/server/flask/__init__.py | 2 +- graphql_env/server/flask/graphql_view.py | 240 +++++++++++------- graphql_env/server/flask/tests/app.py | 7 +- .../server/flask/tests/test_graphql_view.py | 11 +- graphql_env/server/flask/utils.py | 28 +- graphql_env/utils.py | 2 +- 8 files changed, 190 insertions(+), 125 deletions(-) diff --git a/graphql_env/environment.py b/graphql_env/environment.py index d3cf7ad..a6c6803 100644 --- a/graphql_env/environment.py +++ b/graphql_env/environment.py @@ -1,13 +1,12 @@ from .backend import GraphQLBackend, get_default_backend, GraphQLDocument -from .utils import get_unique_query_id, get_unique_schema_id +from .utils import get_unique_document_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): + def __init__(self, schema, backend=None, store=None): self.schema = schema - self.middleware = middleware if backend is None: backend = get_default_backend() else: @@ -22,20 +21,20 @@ 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)) + key = (self.schema_key, get_unique_document_id(source)) document = self.backend.document_from_cache_or_string( self.schema, source, key=key) return document - def load_document(self, query_id): + def load_document(self, document_id): """ - Load a document given a query_id + Load a document given a document_id """ if not self.store: raise Exception( "The GraphQL Environment doesn't have set any store.") - document = self.store[query_id] + document = self.store[document_id] if isinstance(document, string_types): return self.document_from_string(document) @@ -47,8 +46,8 @@ def load_document(self, query_id): format(repr(document))) def get_document_from_params(self, params): - if params.query_id: - return self.load_document(params.query_id) + if params.document_id: + return self.load_document(params.document_id) return self.document_from_string(params.query) def __call__(self, diff --git a/graphql_env/params.py b/graphql_env/params.py index 293e303..4c3efb1 100644 --- a/graphql_env/params.py +++ b/graphql_env/params.py @@ -6,14 +6,14 @@ class GraphQLParams(object): '''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, + document_id=None, operation_name=None, variables=None): - if not query or query_id: + if not query and not document_id: raise GraphQLError("Must provide query string.") if variables and not isinstance(variables, Mapping): @@ -22,6 +22,6 @@ def __init__(self, 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/flask/__init__.py b/graphql_env/server/flask/__init__.py index d657f90..38a3c33 100644 --- a/graphql_env/server/flask/__init__.py +++ b/graphql_env/server/flask/__init__.py @@ -1 +1 @@ -from .graphql_view import graphql_view +from .graphql_view import GraphQLView diff --git a/graphql_env/server/flask/graphql_view.py b/graphql_env/server/flask/graphql_view.py index 132cb6e..2d779a4 100644 --- a/graphql_env/server/flask/graphql_view.py +++ b/graphql_env/server/flask/graphql_view.py @@ -2,106 +2,152 @@ import json import six -from graphql.error import GraphQLError, format_error as default_format_error +from graphql.error import GraphQLError, format_error as format_graphql_error from graphql.execution import ExecutionResult from flask import jsonify, request +from flask.views import View from .graphiql import render_graphiql from .exceptions import GraphQLHTTPError, InvalidJSONError, HTTPMethodNotAllowed -from .utils import params_from_http_request - - -def can_display_graphiql(request): - if request.method != 'GET': - return False - if 'raw' in request.args: - return False - - best = request.accept_mimetypes \ - .best_match(['application/json', 'text/html']) - - return best == 'text/html' and \ - request.accept_mimetypes[best] > \ - request.accept_mimetypes['application/json'] - - -def get_graphql_params(request): - data = {} - content_type = request.mimetype - if content_type == 'application/graphql': - data = {'query': request.data.decode('utf8')} - elif content_type == 'application/json': +from .utils import params_from_http_request, execution_result_to_dict, ALL_OPERATIONS, QUERY_OPERATION +from graphql_env import GraphQLEnvironment, GraphQLCoreBackend + + +class GraphQLView(View): + schema = None + executor = None + root_value = None + env = None + graphiql = False + graphiql_version = None + format_error = None + graphiql_template = None + graphiql_html_title = None + middleware = None + store = None + execute_args = None + batch = False + + methods = ['GET', 'POST', 'PUT', 'DELETE'] + + def __init__(self, **kwargs): + super(GraphQLView, self).__init__() + for key, value in kwargs.items(): + if hasattr(self, key): + setattr(self, key, value) + + if self.batch: + raise Exception("GraphQLView batch is no longer supported.") + + if self.env: + assert not self.schema, ( + 'Cant set env and schema at the same time. Please use GraphQLEnv(schema=...)' + ) + assert not self.store, ( + 'Cant set env and store at the same time. Please use GraphQLEnv(store=...)' + ) + assert not self.store, ( + 'Cant set env and backend at the same time. Please use GraphQLEnv(backend=...)' + ) + else: + self.backend = self.backend or GraphQLCoreBackend() + assert isinstance(self.schema, GraphQLSchema), 'A Schema is required to be provided to GraphQLView.' + self.env = GraphQLEnvironment( + self.schema, + backend=self.backend, + store=self.store + ) + + self.execute_args = self.execute_args or {} + + if self.executor: + self.execute_args['executor'] = self.executor + + def get_root_value(self): + return self.root_value + + def get_context(self): + return request + + def get_middleware(self): + return self.middleware + + def get_executor(self): + return self.executor + + @staticmethod + def format_error(error): + if isinstance(error, GraphQLError): + return format_graphql_error(error) + + return {'message': six.text_type(error)} + + def execute(self, *args, **kwargs): + if self.execute_args: + kwargs = dict(kwargs, **self.execute_args) + return self.env(*args, **kwargs) + + def get_allowed_operations(self): + if request.method == "GET": + return QUERY_OPERATION + return ALL_OPERATIONS + + def serialize(self, execution_result): + data = execution_result_to_dict(execution_result, self.format_error) + return jsonify(data) + + def can_display_graphiql(self): + if request.method != 'GET': + return False + if 'raw' in request.args: + return False + + best = request.accept_mimetypes \ + .best_match(['application/json', 'text/html']) + + return best == 'text/html' and \ + request.accept_mimetypes[best] > \ + request.accept_mimetypes['application/json'] + + def get_graphql_params(self): + data = {} + content_type = request.mimetype + if content_type == 'application/graphql': + data = {'query': request.data.decode('utf8')} + elif content_type == 'application/json': + try: + data = request.get_json() + except: + raise InvalidJSONError() + elif content_type in ('application/x-www-form-urlencoded', + 'multipart/form-data', ): + data = request.form.to_dict() + + query_params = request.args.to_dict() + return params_from_http_request(query_params, data) + + def dispatch_request(self): + graphql_params = None + status = 200 try: - data = request.get_json() - except: - raise InvalidJSONError() - elif content_type in ('application/x-www-form-urlencoded', - 'multipart/form-data', ): - data = request.form.to_dict() - - query_params = request.args.to_dict() - return params_from_http_request(query_params, data) - - -ALL_OPERATIONS = set(("query", "mutation", "subscription")) - - -def get_allowed_operations(request): - if request.method == "GET": - return set(("query", )) - return ALL_OPERATIONS - - -Request = object() - - -def execution_result_to_dict(execution_result, format_error): - data = {} - if execution_result.errors: - data['errors'] = [ - format_error(error) for error in execution_result.errors - ] - if execution_result.data and not execution_result.invalid: - data['data'] = execution_result.data - return data - - -def default_serialize(execution_result, format_error=default_format_error): - data = execution_result_to_dict(execution_result, format_error) - return jsonify(data) #, 200 if execution_result.errors else 400 - - -def graphql_view(execute, - root=None, - graphiql=False, - context=Request, - middleware=None, - serialize=default_serialize, - allowed_operations=None): - graphql_params = None - allowed_operations = get_allowed_operations(request) - status = 200 - try: - if request.method not in ['GET', 'POST']: - raise HTTPMethodNotAllowed() - graphql_params = get_graphql_params(request) - if context is Request: - context = request - execution_result = execute( - graphql_params, - root=root, - context=context, - middleware=middleware, - allowed_operations=allowed_operations) - except Exception as error: - if isinstance(error, GraphQLHTTPError): - status = error.status_code - execution_result = ExecutionResult(errors=[error]) - - if graphiql and can_display_graphiql(request): - return render_graphiql(graphql_params, execution_result) - - if execution_result.data is None and execution_result.errors: - status = 400 - - return serialize(execution_result), status + if request.method not in ['GET', 'POST']: + raise HTTPMethodNotAllowed() + execution_result = self.execute( + self.get_graphql_params(), + root=self.get_root_value(), + context=self.get_context(), + middleware=self.get_middleware(), + allowed_operations=self.get_allowed_operations() + ) + except Exception as error: + if isinstance(error, GraphQLHTTPError): + status = error.status_code + execution_result = ExecutionResult(errors=[error]) + + if self.graphiql and self.can_display_graphiql(): + return self.render_graphiql(graphql_params, execution_result) + + if execution_result.data is None and execution_result.errors: + status = 400 + + return self.serialize(execution_result), status diff --git a/graphql_env/server/flask/tests/app.py b/graphql_env/server/flask/tests/app.py index 0a8c5ff..c9d2126 100644 --- a/graphql_env/server/flask/tests/app.py +++ b/graphql_env/server/flask/tests/app.py @@ -1,5 +1,5 @@ from flask import Flask -from .. import graphql_view +from .. import GraphQLView from .schema import Schema from graphql_env import GraphQLEnvironment, GraphQLCoreBackend @@ -19,12 +19,9 @@ def create_app(path='/graphql', environment=None, **kwargs): # ) ) - def graphql(): - return graphql_view(graphql_env, **kwargs) - app.add_url_rule( path, - view_func=graphql, + view_func=GraphQLView.as_view('graphql', env=graphql_env, **kwargs), methods=['GET', 'POST', 'PUT', 'DELETE'], endpoint='graphql') return app diff --git a/graphql_env/server/flask/tests/test_graphql_view.py b/graphql_env/server/flask/tests/test_graphql_view.py index 8aea767..1bb497a 100644 --- a/graphql_env/server/flask/tests/test_graphql_view.py +++ b/graphql_env/server/flask/tests/test_graphql_view.py @@ -163,6 +163,7 @@ def test_allows_mutation_to_exist_within_a_get(client): def test_allows_post_with_json_encoding(client): response = client.post( url_string(), data=j(query='{test}'), content_type='application/json') + assert response.status_code == 200 assert response_json(response) == {'data': {'test': "Hello World"}} @@ -458,12 +459,12 @@ def test_passes_request_into_request_context(client): assert response_json(response) == {'data': {'request': 'testing'}} -@pytest.mark.parametrize('app', [create_app(context="CUSTOM CONTEXT")]) -def test_supports_pretty_printing(client): - response = client.get(url_string(query='{context}')) +# @pytest.mark.parametrize('app', [create_app(context="CUSTOM CONTEXT")]) +# def test_supports_pretty_printing(client): +# response = client.get(url_string(query='{context}')) - assert response.status_code == 200 - assert response_json(response) == {'data': {'context': 'CUSTOM CONTEXT'}} +# assert response.status_code == 200 +# assert response_json(response) == {'data': {'context': 'CUSTOM CONTEXT'}} def test_post_multipart_data(client): diff --git a/graphql_env/server/flask/utils.py b/graphql_env/server/flask/utils.py index c2cf5b1..2b79c0b 100644 --- a/graphql_env/server/flask/utils.py +++ b/graphql_env/server/flask/utils.py @@ -5,6 +5,15 @@ from .exceptions import InvalidVariablesJSONError, MissingQueryError +QUERY_OPERATION = set(("query",)) + +MUTATION_OPERATION = set(("mutation",)) + +SUBSCRIPTION_OPERATION = set(("subscription",)) + +ALL_OPERATIONS = QUERY_OPERATION | MUTATION_OPERATION | SUBSCRIPTION_OPERATION + + def params_from_http_request(query_params, data): variables = data.get('variables') or query_params.get('variables') if isinstance(variables, six.string_types): @@ -15,7 +24,20 @@ def params_from_http_request(query_params, data): return GraphQLParams( query=data.get('query') or query_params.get('query'), - query_id=data.get('queryId') or query_params.get('queryId'), - operation_name=data.get('operationName') or - query_params.get('operationName'), + document_id=data.get('documentId') or query_params.get('documentId'), + operation_name=( + data.get('operationName') or + query_params.get('operationName') + ), variables=variables) + + +def execution_result_to_dict(execution_result, format_error): + data = {} + if execution_result.errors: + data['errors'] = [ + format_error(error) for error in execution_result.errors + ] + if execution_result.data and not execution_result.invalid: + data['data'] = execution_result.data + return data diff --git a/graphql_env/utils.py b/graphql_env/utils.py index 7d8a58f..4529729 100644 --- a/graphql_env/utils.py +++ b/graphql_env/utils.py @@ -20,7 +20,7 @@ def get_unique_schema_id(schema): return _schemas[schema] -def get_unique_query_id(query_str): +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 {}" From 7911a6a1f38348c426b3f1fe2feddf19a63589ab Mon Sep 17 00:00:00 2001 From: Syrus Akbary Date: Tue, 8 May 2018 11:48:53 +0900 Subject: [PATCH 02/20] Moved format_error outside flask context view --- graphql_env/server/flask/graphql_view.py | 18 ++++++++---------- graphql_env/server/flask/utils.py | 8 ++++++++ 2 files changed, 16 insertions(+), 10 deletions(-) diff --git a/graphql_env/server/flask/graphql_view.py b/graphql_env/server/flask/graphql_view.py index 2d779a4..33fe3e0 100644 --- a/graphql_env/server/flask/graphql_view.py +++ b/graphql_env/server/flask/graphql_view.py @@ -2,14 +2,19 @@ import json import six -from graphql.error import GraphQLError, format_error as format_graphql_error from graphql.execution import ExecutionResult from flask import jsonify, request from flask.views import View from .graphiql import render_graphiql from .exceptions import GraphQLHTTPError, InvalidJSONError, HTTPMethodNotAllowed -from .utils import params_from_http_request, execution_result_to_dict, ALL_OPERATIONS, QUERY_OPERATION +from .utils import ( + params_from_http_request, + execution_result_to_dict, + ALL_OPERATIONS, + QUERY_OPERATION, + format_error +) from graphql_env import GraphQLEnvironment, GraphQLCoreBackend @@ -20,7 +25,7 @@ class GraphQLView(View): env = None graphiql = False graphiql_version = None - format_error = None + format_error = staticmethod(format_error) graphiql_template = None graphiql_html_title = None middleware = None @@ -74,13 +79,6 @@ def get_middleware(self): def get_executor(self): return self.executor - - @staticmethod - def format_error(error): - if isinstance(error, GraphQLError): - return format_graphql_error(error) - - return {'message': six.text_type(error)} def execute(self, *args, **kwargs): if self.execute_args: diff --git a/graphql_env/server/flask/utils.py b/graphql_env/server/flask/utils.py index 2b79c0b..49f222a 100644 --- a/graphql_env/server/flask/utils.py +++ b/graphql_env/server/flask/utils.py @@ -1,6 +1,7 @@ import json import six +from graphql.error import GraphQLError, format_error as format_graphql_error from ...params import GraphQLParams from .exceptions import InvalidVariablesJSONError, MissingQueryError @@ -41,3 +42,10 @@ def execution_result_to_dict(execution_result, format_error): if execution_result.data and not execution_result.invalid: data['data'] = execution_result.data return data + + +def format_error(error): + if isinstance(error, GraphQLError): + return format_graphql_error(error) + + return {'message': six.text_type(error)} From 7d75ea77eaee2bb43c3259d88d52013d7273545c Mon Sep 17 00:00:00 2001 From: Syrus Akbary Date: Tue, 8 May 2018 11:50:35 +0900 Subject: [PATCH 03/20] Removed unnecessary execute_args option in GraphQL view --- graphql_env/server/flask/graphql_view.py | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/graphql_env/server/flask/graphql_view.py b/graphql_env/server/flask/graphql_view.py index 33fe3e0..6cbe96f 100644 --- a/graphql_env/server/flask/graphql_view.py +++ b/graphql_env/server/flask/graphql_view.py @@ -30,7 +30,6 @@ class GraphQLView(View): graphiql_html_title = None middleware = None store = None - execute_args = None batch = False methods = ['GET', 'POST', 'PUT', 'DELETE'] @@ -62,11 +61,6 @@ def __init__(self, **kwargs): backend=self.backend, store=self.store ) - - self.execute_args = self.execute_args or {} - - if self.executor: - self.execute_args['executor'] = self.executor def get_root_value(self): return self.root_value @@ -81,8 +75,8 @@ def get_executor(self): return self.executor def execute(self, *args, **kwargs): - if self.execute_args: - kwargs = dict(kwargs, **self.execute_args) + if self.executor: + kwargs['executor'] = self.executor return self.env(*args, **kwargs) def get_allowed_operations(self): From aa61675c989190e202f1cf0b753c0407b0986254 Mon Sep 17 00:00:00 2001 From: Syrus Akbary Date: Tue, 8 May 2018 11:53:04 +0900 Subject: [PATCH 04/20] Fix test requirements --- requirements_dev.txt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/requirements_dev.txt b/requirements_dev.txt index 33f259f..f46f570 100644 --- a/requirements_dev.txt +++ b/requirements_dev.txt @@ -3,6 +3,8 @@ bumpversion==0.5.3 wheel==0.29.0 watchdog==0.8.3 flake8==2.6.0 +flask>=0.7.0 +pytest-flask>=0.7.0 tox==2.3.1 coverage==4.1 Sphinx==1.4.8 From db089a0557586e36e81d779a7e08d8f3f4160bcf Mon Sep 17 00:00:00 2001 From: Syrus Akbary Date: Tue, 8 May 2018 12:00:50 +0900 Subject: [PATCH 05/20] Move tests into package --- {tests => graphql_env/tests}/__init__.py | 0 {tests => graphql_env/tests}/schema.py | 0 {tests => graphql_env/tests}/test_graphql_environment.py | 0 3 files changed, 0 insertions(+), 0 deletions(-) rename {tests => graphql_env/tests}/__init__.py (100%) rename {tests => graphql_env/tests}/schema.py (100%) rename {tests => graphql_env/tests}/test_graphql_environment.py (100%) diff --git a/tests/__init__.py b/graphql_env/tests/__init__.py similarity index 100% rename from tests/__init__.py rename to graphql_env/tests/__init__.py diff --git a/tests/schema.py b/graphql_env/tests/schema.py similarity index 100% rename from tests/schema.py rename to graphql_env/tests/schema.py diff --git a/tests/test_graphql_environment.py b/graphql_env/tests/test_graphql_environment.py similarity index 100% rename from tests/test_graphql_environment.py rename to graphql_env/tests/test_graphql_environment.py From 9e9f6519261f2459a422859fb15cb1068253cfc5 Mon Sep 17 00:00:00 2001 From: Syrus Akbary Date: Tue, 8 May 2018 12:40:58 +0900 Subject: [PATCH 06/20] Improved tests --- graphql_env/server/flask/exceptions.py | 1 + graphql_env/server/flask/graphql_view.py | 10 ++++++++-- .../server/flask/tests/test_graphql_view.py | 14 +++++++------- 3 files changed, 16 insertions(+), 9 deletions(-) diff --git a/graphql_env/server/flask/exceptions.py b/graphql_env/server/flask/exceptions.py index 6d47eb1..0d0d4a9 100644 --- a/graphql_env/server/flask/exceptions.py +++ b/graphql_env/server/flask/exceptions.py @@ -19,6 +19,7 @@ class InvalidVariablesJSONError(GraphQLHTTPError): class HTTPMethodNotAllowed(GraphQLHTTPError): + status_code = 405 default_detail = 'GraphQL only supports GET and POST requests.' diff --git a/graphql_env/server/flask/graphql_view.py b/graphql_env/server/flask/graphql_view.py index 6cbe96f..6d93207 100644 --- a/graphql_env/server/flask/graphql_view.py +++ b/graphql_env/server/flask/graphql_view.py @@ -139,7 +139,13 @@ def dispatch_request(self): if self.graphiql and self.can_display_graphiql(): return self.render_graphiql(graphql_params, execution_result) - if execution_result.data is None and execution_result.errors: - status = 400 + # If no data was included in the result, that indicates a runtime query + # error, indicate as such with a generic status code. + # Note: Information about the error itself will still be contained in + # the resulting JSON payload. + # http://facebook.github.io/graphql/#sec-Data + + if status == 200 and execution_result and execution_result.data is None: + status = 500 return self.serialize(execution_result), status diff --git a/graphql_env/server/flask/tests/test_graphql_view.py b/graphql_env/server/flask/tests/test_graphql_view.py index 1bb497a..15ad857 100644 --- a/graphql_env/server/flask/tests/test_graphql_view.py +++ b/graphql_env/server/flask/tests/test_graphql_view.py @@ -108,7 +108,7 @@ def test_errors_when_missing_operation_name(client): mutation TestMutation { writeTest { test } } ''')) - # assert response.status_code == 400 + assert response.status_code == 500 assert response_json(response) == { 'errors': [{ 'message': @@ -382,7 +382,7 @@ def test_handles_syntax_errors_caught_by_graphql(client): def test_handles_errors_caused_by_a_lack_of_query(client): response = client.get(url_string()) - assert response.status_code == 400 + assert response.status_code == 500 assert response_json(response) == { 'errors': [{ 'message': 'Must provide query string.' @@ -459,12 +459,12 @@ def test_passes_request_into_request_context(client): assert response_json(response) == {'data': {'request': 'testing'}} -# @pytest.mark.parametrize('app', [create_app(context="CUSTOM CONTEXT")]) -# def test_supports_pretty_printing(client): -# response = client.get(url_string(query='{context}')) +@pytest.mark.parametrize('app', [create_app(get_context=lambda:"CUSTOM CONTEXT")]) +def test_supports_pretty_printing(client): + response = client.get(url_string(query='{context}')) -# assert response.status_code == 200 -# assert response_json(response) == {'data': {'context': 'CUSTOM CONTEXT'}} + assert response.status_code == 200 + assert response_json(response) == {'data': {'context': 'CUSTOM CONTEXT'}} def test_post_multipart_data(client): From 4de80e7ae063107288b804ff85feb77665fa735c Mon Sep 17 00:00:00 2001 From: Syrus Akbary Date: Tue, 8 May 2018 12:43:16 +0900 Subject: [PATCH 07/20] Removed unused function --- graphql_env/server/flask/graphql_view.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/graphql_env/server/flask/graphql_view.py b/graphql_env/server/flask/graphql_view.py index 6d93207..d97ccf1 100644 --- a/graphql_env/server/flask/graphql_view.py +++ b/graphql_env/server/flask/graphql_view.py @@ -71,9 +71,6 @@ def get_context(self): def get_middleware(self): return self.middleware - def get_executor(self): - return self.executor - def execute(self, *args, **kwargs): if self.executor: kwargs['executor'] = self.executor From 2b994044326eeb9d3536fda3d572bfca7b1e7af2 Mon Sep 17 00:00:00 2001 From: Syrus Akbary Date: Thu, 10 May 2018 12:05:55 +0900 Subject: [PATCH 08/20] Refactored cache out of the backend core logic --- graphql_env/__init__.py | 5 ++-- graphql_env/backend/__init__.py | 2 ++ graphql_env/backend/base.py | 18 ++--------- graphql_env/backend/cache.py | 28 +++++++++++++++++ graphql_env/backend/decider.py | 30 ++++--------------- graphql_env/environment.py | 7 +---- graphql_env/tests/test_graphql_environment.py | 27 +++++++---------- 7 files changed, 52 insertions(+), 65 deletions(-) create mode 100644 graphql_env/backend/cache.py diff --git a/graphql_env/__init__.py b/graphql_env/__init__.py index 2cf1308..1fe786f 100644 --- a/graphql_env/__init__.py +++ b/graphql_env/__init__.py @@ -7,8 +7,8 @@ from .environment import GraphQLEnvironment from .backend import (GraphQLBackend, GraphQLDocument, GraphQLCoreBackend, - GraphQLDeciderBackend, get_default_backend, - set_default_backend) + GraphQLDeciderBackend, GraphQLCachedBackend, + get_default_backend, set_default_backend) GraphQLEnv = GraphQLEnvironment @@ -17,6 +17,7 @@ 'GraphQLEnvironment', 'GraphQLBackend', 'GraphQLDocument', + 'GraphQLCachedBackend', 'GraphQLCoreBackend', 'GraphQLDeciderBackend', 'get_default_backend', diff --git a/graphql_env/backend/__init__.py b/graphql_env/backend/__init__.py index 6283880..080de97 100644 --- a/graphql_env/backend/__init__.py +++ b/graphql_env/backend/__init__.py @@ -1,6 +1,7 @@ from .base import GraphQLBackend, GraphQLDocument from .core import GraphQLCoreBackend from .decider import GraphQLDeciderBackend +from .cache import GraphQLCachedBackend _default_backend = GraphQLCoreBackend() @@ -22,6 +23,7 @@ def set_default_backend(backend): '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..5dc218e 100644 --- a/graphql_env/backend/base.py +++ b/graphql_env/backend/base.py @@ -1,20 +1,6 @@ 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( diff --git a/graphql_env/backend/cache.py b/graphql_env/backend/cache.py new file mode 100644 index 0000000..6343cec --- /dev/null +++ b/graphql_env/backend/cache.py @@ -0,0 +1,28 @@ +from ..utils import get_unique_document_id, get_unique_schema_id +from .base import GraphQLBackend + + +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''' + 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/decider.py b/graphql_env/backend/decider.py index f1f7cae..c243cb5 100644 --- a/graphql_env/backend/decider.py +++ b/graphql_env/backend/decider.py @@ -2,40 +2,22 @@ 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, e: + print(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))) diff --git a/graphql_env/environment.py b/graphql_env/environment.py index a6c6803..caa7ae9 100644 --- a/graphql_env/environment.py +++ b/graphql_env/environment.py @@ -1,5 +1,4 @@ from .backend import GraphQLBackend, get_default_backend, GraphQLDocument -from .utils import get_unique_document_id, get_unique_schema_id from ._compat import string_types from .params import GraphQLParams @@ -15,16 +14,12 @@ def __init__(self, schema, backend=None, store=None): 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_document_id(source)) - document = self.backend.document_from_cache_or_string( - self.schema, source, key=key) - return document + return self.backend.document_from_string(self.schema, source) def load_document(self, document_id): """ diff --git a/graphql_env/tests/test_graphql_environment.py b/graphql_env/tests/test_graphql_environment.py index cfba54d..7d27e12 100644 --- a/graphql_env/tests/test_graphql_environment.py +++ b/graphql_env/tests/test_graphql_environment.py @@ -4,15 +4,13 @@ import pytest -from graphql_env import GraphQLEnv, GraphQLBackend, GraphQLCoreBackend, GraphQLDocument, GraphQLDeciderBackend +from graphql_env import GraphQLEnv, GraphQLBackend, GraphQLCoreBackend, GraphQLDocument, GraphQLDeciderBackend, GraphQLCachedBackend from graphql.execution.executors.sync import SyncExecutor from .schema import schema def test_core_backend(): """Sample pytest test function with the pytest fixture as an argument.""" - # from bs4 import BeautifulSoup - # assert 'GitHub' in BeautifulSoup(response.content).title.string graphql_env = GraphQLEnv(schema=schema, backend=GraphQLCoreBackend()) document = graphql_env.document_from_string('{ hello }') assert isinstance(document, GraphQLDocument) @@ -20,25 +18,20 @@ def test_core_backend(): assert not result == {'data': {'hello': 'World'}} -def test_backend_is_cached_by_default(): +def test_backend_is_not_cached_by_default(): """Sample pytest test function with the pytest fixture as an argument.""" - # from bs4 import BeautifulSoup - # assert 'GitHub' in BeautifulSoup(response.content).title.string graphql_env = GraphQLEnv(schema=schema, backend=GraphQLCoreBackend()) document1 = graphql_env.document_from_string('{ hello }') document2 = graphql_env.document_from_string('{ hello }') - assert document1 == document2 + assert document1 != document2 -def test_backend_will_compute_if_cache_non_existing(): +def test_backend_is_cached_when_needed(): """Sample pytest test function with the pytest fixture as an argument.""" - # from bs4 import BeautifulSoup - # assert 'GitHub' in BeautifulSoup(response.content).title.string - graphql_env = GraphQLEnv( - schema=schema, backend=GraphQLCoreBackend(use_cache=False)) + graphql_env = GraphQLEnv(schema=schema, backend=GraphQLCachedBackend(GraphQLCoreBackend())) document1 = graphql_env.document_from_string('{ hello }') document2 = graphql_env.document_from_string('{ hello }') - assert document1 != document2 + assert document1 == document2 def test_backend_can_execute(): @@ -74,7 +67,7 @@ class FakeBackend(GraphQLBackend): def __init__(self, raises=False): self.raises = raises - def document_from_cache_or_string(self, *args, **kwargs): + def document_from_string(self, *args, **kwargs): self.reached = True if self.raises: raise Exception("Backend failed") @@ -135,9 +128,9 @@ def test_decider_backend_use_cache_if_provided(): graphql_env = GraphQLEnv( schema=schema, backend=GraphQLDeciderBackend([ - backend1, - backend2, - ], cache={})) + GraphQLCachedBackend(backend1), + GraphQLCachedBackend(backend2), + ])) graphql_env.document_from_string('{ hello }') assert backend1.reached From 86dfac8dcc8da66bf6a92b921b7278f34da696e7 Mon Sep 17 00:00:00 2001 From: Syrus Akbary Date: Thu, 10 May 2018 12:28:56 +0900 Subject: [PATCH 09/20] Improved schema and document hashing --- graphql_env/backend/cache.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/graphql_env/backend/cache.py b/graphql_env/backend/cache.py index 6343cec..8d60aa5 100644 --- a/graphql_env/backend/cache.py +++ b/graphql_env/backend/cache.py @@ -14,9 +14,10 @@ def __init__(self, backend, cache_map=None): 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''' - schema_id = get_unique_schema_id(schema) - document_id = get_unique_document_id(request_string) - return (schema_id, document_id) + 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)''' From 4b5b5bb59ca1525b94cba6dd47d56eefe9c31ca9 Mon Sep 17 00:00:00 2001 From: Syrus Akbary Date: Tue, 29 May 2018 20:20:09 -0700 Subject: [PATCH 10/20] Removed GraphQLEnvironment dep in tests --- graphql_env/server/flask/graphql_view.py | 4 +- graphql_env/tests/test_graphql_environment.py | 78 +++++++++---------- 2 files changed, 38 insertions(+), 44 deletions(-) diff --git a/graphql_env/server/flask/graphql_view.py b/graphql_env/server/flask/graphql_view.py index d97ccf1..b0e103b 100644 --- a/graphql_env/server/flask/graphql_view.py +++ b/graphql_env/server/flask/graphql_view.py @@ -15,7 +15,7 @@ QUERY_OPERATION, format_error ) -from graphql_env import GraphQLEnvironment, GraphQLCoreBackend +from graphql_env import GraphQLEnvironment, get_default_backend class GraphQLView(View): @@ -54,7 +54,7 @@ def __init__(self, **kwargs): 'Cant set env and backend at the same time. Please use GraphQLEnv(backend=...)' ) else: - self.backend = self.backend or GraphQLCoreBackend() + self.backend = self.backend or get_default_backend() assert isinstance(self.schema, GraphQLSchema), 'A Schema is required to be provided to GraphQLView.' self.env = GraphQLEnvironment( self.schema, diff --git a/graphql_env/tests/test_graphql_environment.py b/graphql_env/tests/test_graphql_environment.py index 7d27e12..0364891 100644 --- a/graphql_env/tests/test_graphql_environment.py +++ b/graphql_env/tests/test_graphql_environment.py @@ -4,15 +4,15 @@ import pytest -from graphql_env import GraphQLEnv, GraphQLBackend, GraphQLCoreBackend, GraphQLDocument, GraphQLDeciderBackend, GraphQLCachedBackend +from graphql_env import GraphQLBackend, GraphQLCoreBackend, GraphQLDocument, GraphQLDeciderBackend, GraphQLCachedBackend from graphql.execution.executors.sync import SyncExecutor from .schema import schema def test_core_backend(): """Sample pytest test function with the pytest fixture as an argument.""" - graphql_env = GraphQLEnv(schema=schema, backend=GraphQLCoreBackend()) - document = graphql_env.document_from_string('{ hello }') + backend = GraphQLCoreBackend() + document = backend.document_from_string(schema, '{ hello }') assert isinstance(document, GraphQLDocument) result = document.execute() assert not result == {'data': {'hello': 'World'}} @@ -20,23 +20,23 @@ def test_core_backend(): def test_backend_is_not_cached_by_default(): """Sample pytest test function with the pytest fixture as an argument.""" - graphql_env = GraphQLEnv(schema=schema, backend=GraphQLCoreBackend()) - document1 = graphql_env.document_from_string('{ hello }') - document2 = graphql_env.document_from_string('{ hello }') + backend = GraphQLCoreBackend() + document1 = backend.document_from_string(schema, '{ hello }') + document2 = backend.document_from_string(schema, '{ hello }') assert document1 != document2 def test_backend_is_cached_when_needed(): """Sample pytest test function with the pytest fixture as an argument.""" - graphql_env = GraphQLEnv(schema=schema, backend=GraphQLCachedBackend(GraphQLCoreBackend())) - document1 = graphql_env.document_from_string('{ hello }') - document2 = graphql_env.document_from_string('{ hello }') + cached_backend = GraphQLCachedBackend(GraphQLCoreBackend()) + document1 = cached_backend.document_from_string(schema, '{ hello }') + document2 = cached_backend.document_from_string(schema, '{ hello }') assert document1 == document2 def test_backend_can_execute(): - graphql_env = GraphQLEnv(schema=schema, backend=GraphQLCoreBackend()) - document1 = graphql_env.document_from_string('{ hello }') + backend = GraphQLCoreBackend() + document1 = backend.document_from_string(schema, '{ hello }') result = document1.execute() assert not result.errors assert result.data == {'hello': 'World'} @@ -52,9 +52,8 @@ def execute(self, *args, **kwargs): def test_backend_can_execute_custom_executor(): executor = BaseExecutor() - graphql_env = GraphQLEnv( - schema=schema, backend=GraphQLCoreBackend(executor=executor)) - document1 = graphql_env.document_from_string('{ hello }') + backend = GraphQLCoreBackend(executor=executor) + document1 = backend.document_from_string(schema, '{ hello }') result = document1.execute() assert not result.errors assert result.data == {'hello': 'World'} @@ -79,13 +78,12 @@ def reset(self): def test_decider_backend_healthy_backend(): backend1 = FakeBackend() backend2 = FakeBackend() - graphql_env = GraphQLEnv( - schema=schema, backend=GraphQLDeciderBackend([ - backend1, - backend2, - ])) + decider_backend = GraphQLDeciderBackend([ + backend1, + backend2, + ]) - graphql_env.document_from_string('{ hello }') + decider_backend.document_from_string(schema, '{ hello }') assert backend1.reached assert not backend2.reached @@ -93,13 +91,12 @@ def test_decider_backend_healthy_backend(): def test_decider_backend_unhealthy_backend(): backend1 = FakeBackend(raises=True) backend2 = FakeBackend() - graphql_env = GraphQLEnv( - schema=schema, backend=GraphQLDeciderBackend([ - backend1, - backend2, - ])) + decider_backend = GraphQLDeciderBackend([ + backend1, + backend2, + ]) - graphql_env.document_from_string('{ hello }') + decider_backend.document_from_string(schema, '{ hello }') assert backend1.reached assert backend2.reached @@ -107,35 +104,32 @@ def test_decider_backend_unhealthy_backend(): def test_decider_backend_dont_use_cache(): backend1 = FakeBackend() backend2 = FakeBackend() - graphql_env = GraphQLEnv( - schema=schema, backend=GraphQLDeciderBackend([ - backend1, - backend2, - ])) + decider_backend = GraphQLDeciderBackend([ + backend1, + backend2, + ]) - graphql_env.document_from_string('{ hello }') + decider_backend.document_from_string(schema, '{ hello }') assert backend1.reached assert not backend2.reached backend1.reset() - graphql_env.document_from_string('{ hello }') + decider_backend.document_from_string(schema, '{ hello }') assert backend1.reached def test_decider_backend_use_cache_if_provided(): backend1 = FakeBackend() backend2 = FakeBackend() - graphql_env = GraphQLEnv( - schema=schema, - backend=GraphQLDeciderBackend([ - GraphQLCachedBackend(backend1), - GraphQLCachedBackend(backend2), - ])) - - graphql_env.document_from_string('{ hello }') + decider_backend = GraphQLDeciderBackend([ + GraphQLCachedBackend(backend1), + GraphQLCachedBackend(backend2), + ]) + + decider_backend.document_from_string(schema, '{ hello }') assert backend1.reached assert not backend2.reached backend1.reset() - graphql_env.document_from_string('{ hello }') + decider_backend.document_from_string(schema, '{ hello }') assert not backend1.reached From b044501113472ac269fcc69a647d84b3490fca46 Mon Sep 17 00:00:00 2001 From: Syrus Akbary Date: Tue, 29 May 2018 22:03:20 -0700 Subject: [PATCH 11/20] Removed print --- graphql_env/backend/decider.py | 1 - 1 file changed, 1 deletion(-) diff --git a/graphql_env/backend/decider.py b/graphql_env/backend/decider.py index c243cb5..ba4402f 100644 --- a/graphql_env/backend/decider.py +++ b/graphql_env/backend/decider.py @@ -15,7 +15,6 @@ def document_from_string(self, schema, request_string): try: return backend.document_from_string(schema, request_string) except Exception, e: - print(e) continue raise Exception( From 0bf0b0fa5faa3627427f6d41c081d22145be5db0 Mon Sep 17 00:00:00 2001 From: Syrus Akbary Date: Tue, 29 May 2018 22:36:04 -0700 Subject: [PATCH 12/20] Improved code formatting. Improved Class view --- graphql_env/__init__.py | 34 +- graphql_env/_compat.py | 17 +- graphql_env/backend/decider.py | 8 +- graphql_env/environment.py | 30 +- graphql_env/params.py | 18 +- graphql_env/server/flask/__init__.py | 2 + graphql_env/server/flask/exceptions.py | 8 +- graphql_env/server/flask/graphiql.py | 21 +- graphql_env/server/flask/graphql_view.py | 123 ++++--- graphql_env/server/flask/tests/__init__.py | 2 +- graphql_env/server/flask/tests/app.py | 11 +- graphql_env/server/flask/tests/schema.py | 47 +-- .../server/flask/tests/test_graphql_view.py | 325 ++++++++---------- graphql_env/server/flask/utils.py | 22 +- graphql_env/tests/test_graphql_environment.py | 62 ++-- graphql_env/utils.py | 8 +- 16 files changed, 383 insertions(+), 355 deletions(-) diff --git a/graphql_env/__init__.py b/graphql_env/__init__.py index 1fe786f..c19623c 100644 --- a/graphql_env/__init__.py +++ b/graphql_env/__init__.py @@ -2,24 +2,30 @@ """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, GraphQLCachedBackend, - get_default_backend, set_default_backend) +from .backend import ( + GraphQLBackend, + GraphQLDocument, + GraphQLCoreBackend, + GraphQLDeciderBackend, + GraphQLCachedBackend, + get_default_backend, + set_default_backend, +) GraphQLEnv = GraphQLEnvironment __all__ = [ - 'GraphQLEnv', - 'GraphQLEnvironment', - 'GraphQLBackend', - 'GraphQLDocument', - 'GraphQLCachedBackend', - 'GraphQLCoreBackend', - 'GraphQLDeciderBackend', - 'get_default_backend', - 'set_default_backend', + "GraphQLEnv", + "GraphQLEnvironment", + "GraphQLBackend", + "GraphQLDocument", + "GraphQLCachedBackend", + "GraphQLCoreBackend", + "GraphQLDeciderBackend", + "get_default_backend", + "set_default_backend", ] 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/decider.py b/graphql_env/backend/decider.py index ba4402f..20c67dc 100644 --- a/graphql_env/backend/decider.py +++ b/graphql_env/backend/decider.py @@ -14,9 +14,11 @@ def document_from_string(self, schema, request_string): for backend in self.backends: try: return backend.document_from_string(schema, request_string) - except Exception, e: + except Exception as e: 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/environment.py b/graphql_env/environment.py index caa7ae9..3fa704a 100644 --- a/graphql_env/environment.py +++ b/graphql_env/environment.py @@ -10,8 +10,8 @@ def __init__(self, schema, backend=None, store=None): backend = get_default_backend() else: assert isinstance( - backend, - GraphQLBackend), "backend must be instance of GraphQLBackend" + backend, GraphQLBackend + ), "backend must be instance of GraphQLBackend" self.backend = backend self.store = store @@ -26,8 +26,7 @@ def load_document(self, document_id): Load a document given a document_id """ if not self.store: - raise Exception( - "The GraphQL Environment doesn't have set any store.") + raise Exception("The GraphQL Environment doesn't have set any store.") document = self.store[document_id] @@ -37,20 +36,24 @@ def load_document(self, document_id): return document raise Exception( - "Document returned from the store must be an string or a GraphQLDocument. Received {}.". - format(repr(document))) + "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.document_id: return self.load_document(params.document_id) return self.document_from_string(params.query) - def __call__(self, - graphql_params, - root=None, - context=None, - middleware=None, - allowed_operations=None): + 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." @@ -62,4 +65,5 @@ def __call__(self, middleware=middleware, operation_name=graphql_params.operation_name, variables=graphql_params.variables, - allowed_operations=allowed_operations) + allowed_operations=allowed_operations, + ) diff --git a/graphql_env/params.py b/graphql_env/params.py index 4c3efb1..35f48fa 100644 --- a/graphql_env/params.py +++ b/graphql_env/params.py @@ -4,22 +4,22 @@ 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', 'document_id', 'operation_name', 'variables') + __slots__ = ("query", "document_id", "operation_name", "variables") - def __init__(self, - query=None, - document_id=None, - operation_name=None, - variables=None): + 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.document_id = document_id diff --git a/graphql_env/server/flask/__init__.py b/graphql_env/server/flask/__init__.py index 38a3c33..ad5e48b 100644 --- a/graphql_env/server/flask/__init__.py +++ b/graphql_env/server/flask/__init__.py @@ -1 +1,3 @@ from .graphql_view import GraphQLView + +__all__ = ["GraphQLView"] diff --git a/graphql_env/server/flask/exceptions.py b/graphql_env/server/flask/exceptions.py index 0d0d4a9..4392779 100644 --- a/graphql_env/server/flask/exceptions.py +++ b/graphql_env/server/flask/exceptions.py @@ -10,18 +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): status_code = 405 - default_detail = 'GraphQL only supports GET and POST requests.' + 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/graphiql.py b/graphql_env/server/flask/graphiql.py index c115b38..dbaea10 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 = '''