Skip to content

First iteration of a class-based view for GraphQL env flask #2

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 20 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 20 additions & 16 deletions graphql_env/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,23 +2,27 @@
"""Top-level package for GraphQL Environment."""

__author__ = """Syrus Akbary"""
__email__ = '[email protected]'
__version__ = '0.1.0'
__email__ = "[email protected]"
__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",
]
17 changes: 10 additions & 7 deletions graphql_env/_compat.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,22 +11,23 @@
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())
iteritems = lambda d: iter(d.items())

import pickle
from io import BytesIO, StringIO

NativeStringIO = StringIO

def reraise(tp, value, tb=None):
Expand Down Expand Up @@ -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):
Expand All @@ -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


Expand All @@ -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:
Expand Down
23 changes: 14 additions & 9 deletions graphql_env/backend/__init__.py
Original file line number Diff line number Diff line change
@@ -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",
]
25 changes: 6 additions & 19 deletions graphql_env/backend/base.py
Original file line number Diff line number Diff line change
@@ -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):
Expand All @@ -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__)
)
31 changes: 31 additions & 0 deletions graphql_env/backend/cache.py
Original file line number Diff line number Diff line change
@@ -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]
12 changes: 6 additions & 6 deletions graphql_env/backend/compiled.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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)
return self.execute_func(*args, **kwargs)
35 changes: 22 additions & 13 deletions graphql_env/backend/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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

Expand All @@ -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,
Expand All @@ -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])
35 changes: 9 additions & 26 deletions graphql_env/backend/decider.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
)
)
Loading