| 
 | 1 | +import json  | 
 | 2 | +from collections import namedtuple, MutableMapping  | 
 | 3 | + | 
 | 4 | +import six  | 
 | 5 | +from graphql import Source, execute, parse, validate  | 
 | 6 | +from graphql.error import format_error as format_graphql_error  | 
 | 7 | +from graphql.error import GraphQLError  | 
 | 8 | +from graphql.execution import ExecutionResult  | 
 | 9 | +from graphql.type.schema import GraphQLSchema  | 
 | 10 | +from graphql.utils.get_operation_ast import get_operation_ast  | 
 | 11 | + | 
 | 12 | +from .error import HttpQueryError  | 
 | 13 | + | 
 | 14 | + | 
 | 15 | +class SkipException(Exception):  | 
 | 16 | +    pass  | 
 | 17 | + | 
 | 18 | + | 
 | 19 | +GraphQLParams = namedtuple('GraphQLParams', 'query,variables,operation_name')  | 
 | 20 | +GraphQLResponse = namedtuple('GraphQLResponse', 'result,status_code')  | 
 | 21 | + | 
 | 22 | + | 
 | 23 | +def default_format_error(error):  | 
 | 24 | +    if isinstance(error, GraphQLError):  | 
 | 25 | +        return format_graphql_error(error)  | 
 | 26 | + | 
 | 27 | +    return {'message': six.text_type(error)}  | 
 | 28 | + | 
 | 29 | + | 
 | 30 | +def run_http_query(schema, request_method, data, query_data=None, batch_enabled=False, catch=False, **execute_options):  | 
 | 31 | +    if request_method not in ('get', 'post'):  | 
 | 32 | +        raise HttpQueryError(  | 
 | 33 | +            405,  | 
 | 34 | +            'GraphQL only supports GET and POST requests.',  | 
 | 35 | +            headers={  | 
 | 36 | +                'Allow': 'GET, POST'  | 
 | 37 | +            }  | 
 | 38 | +        )  | 
 | 39 | +    if catch:  | 
 | 40 | +        catch = HttpQueryError  | 
 | 41 | +    else:  | 
 | 42 | +        catch = SkipException  | 
 | 43 | +    is_batch = isinstance(data, list)  | 
 | 44 | + | 
 | 45 | +    is_get_request = request_method == 'get'  | 
 | 46 | +    allow_only_query = is_get_request  | 
 | 47 | + | 
 | 48 | +    if not is_batch:  | 
 | 49 | +        if not isinstance(data, (dict, MutableMapping)):  | 
 | 50 | +            raise HttpQueryError(  | 
 | 51 | +                400,  | 
 | 52 | +                'GraphQL params should be a dict. Received {}.'.format(data)  | 
 | 53 | +            )  | 
 | 54 | +        data = [data]  | 
 | 55 | +    elif not batch_enabled:  | 
 | 56 | +        raise HttpQueryError(  | 
 | 57 | +            400,  | 
 | 58 | +            'Batch GraphQL requests are not enabled.'  | 
 | 59 | +        )  | 
 | 60 | + | 
 | 61 | +    if not data:  | 
 | 62 | +        raise HttpQueryError(  | 
 | 63 | +            400,  | 
 | 64 | +            'Received an empty list in the batch request.'  | 
 | 65 | +        )  | 
 | 66 | + | 
 | 67 | +    extra_data = {}  | 
 | 68 | +    # If is a batch request, we don't consume the data from the query  | 
 | 69 | +    if not is_batch:  | 
 | 70 | +        extra_data = query_data or {}  | 
 | 71 | + | 
 | 72 | +    all_params = [get_graphql_params(entry, extra_data) for entry in data]  | 
 | 73 | + | 
 | 74 | +    responses = [get_response(  | 
 | 75 | +        schema,  | 
 | 76 | +        params,  | 
 | 77 | +        catch,  | 
 | 78 | +        allow_only_query,  | 
 | 79 | +        **execute_options  | 
 | 80 | +    ) for params in all_params]  | 
 | 81 | + | 
 | 82 | +    return responses, all_params  | 
 | 83 | + | 
 | 84 | + | 
 | 85 | +def encode_execution_results(execution_results, format_error, is_batch, encode):  | 
 | 86 | +    responses = [  | 
 | 87 | +        format_execution_result(execution_result, format_error)  | 
 | 88 | +        for execution_result in execution_results  | 
 | 89 | +    ]  | 
 | 90 | +    result, status_codes = zip(*responses)  | 
 | 91 | +    status_code = max(status_codes)  | 
 | 92 | + | 
 | 93 | +    if not is_batch:  | 
 | 94 | +        result = result[0]  | 
 | 95 | + | 
 | 96 | +    return encode(result), status_code  | 
 | 97 | + | 
 | 98 | + | 
 | 99 | +def json_encode(data, pretty=False):  | 
 | 100 | +    if not pretty:  | 
 | 101 | +        return json.dumps(data, separators=(',', ':'))  | 
 | 102 | + | 
 | 103 | +    return json.dumps(  | 
 | 104 | +        data,  | 
 | 105 | +        indent=2,  | 
 | 106 | +        separators=(',', ': ')  | 
 | 107 | +    )  | 
 | 108 | + | 
 | 109 | + | 
 | 110 | +def load_json_variables(variables):  | 
 | 111 | +    if variables and isinstance(variables, six.string_types):  | 
 | 112 | +        try:  | 
 | 113 | +            return json.loads(variables)  | 
 | 114 | +        except:  | 
 | 115 | +            raise HttpQueryError(400, 'Variables are invalid JSON.')  | 
 | 116 | +    return variables  | 
 | 117 | + | 
 | 118 | + | 
 | 119 | +def get_graphql_params(data, query_data):  | 
 | 120 | +    query = data.get('query') or query_data.get('query')  | 
 | 121 | +    variables = data.get('variables') or query_data.get('variables')  | 
 | 122 | +    # id = data.get('id')  | 
 | 123 | +    operation_name = data.get('operationName') or query_data.get('operationName')  | 
 | 124 | + | 
 | 125 | +    return GraphQLParams(query, load_json_variables(variables), operation_name)  | 
 | 126 | + | 
 | 127 | + | 
 | 128 | +def get_response(schema, params, catch=None, allow_only_query=False, **kwargs):  | 
 | 129 | +    try:  | 
 | 130 | +        execution_result = execute_graphql_request(  | 
 | 131 | +            schema,  | 
 | 132 | +            params,  | 
 | 133 | +            allow_only_query,  | 
 | 134 | +            **kwargs  | 
 | 135 | +        )  | 
 | 136 | +    except catch:  | 
 | 137 | +        return None  | 
 | 138 | + | 
 | 139 | +    return execution_result  | 
 | 140 | + | 
 | 141 | + | 
 | 142 | +def format_execution_result(execution_result, format_error):  | 
 | 143 | +    status_code = 200  | 
 | 144 | + | 
 | 145 | +    if execution_result:  | 
 | 146 | +        response = {}  | 
 | 147 | + | 
 | 148 | +        if execution_result.errors:  | 
 | 149 | +            response['errors'] = [format_error(e) for e in execution_result.errors]  | 
 | 150 | + | 
 | 151 | +        if execution_result.invalid:  | 
 | 152 | +            status_code = 400  | 
 | 153 | +        else:  | 
 | 154 | +            status_code = 200  | 
 | 155 | +            response['data'] = execution_result.data  | 
 | 156 | + | 
 | 157 | +    else:  | 
 | 158 | +        response = None  | 
 | 159 | + | 
 | 160 | +    return GraphQLResponse(response, status_code)  | 
 | 161 | + | 
 | 162 | + | 
 | 163 | +def execute_graphql_request(schema, params, allow_only_query=False, **kwargs):  | 
 | 164 | +    if not params.query:  | 
 | 165 | +        raise HttpQueryError(400, 'Must provide query string.')  | 
 | 166 | + | 
 | 167 | +    try:  | 
 | 168 | +        source = Source(params.query, name='GraphQL request')  | 
 | 169 | +        ast = parse(source)  | 
 | 170 | +        validation_errors = validate(schema, ast)  | 
 | 171 | +        if validation_errors:  | 
 | 172 | +            return ExecutionResult(  | 
 | 173 | +                errors=validation_errors,  | 
 | 174 | +                invalid=True,  | 
 | 175 | +            )  | 
 | 176 | +    except Exception as e:  | 
 | 177 | +        return ExecutionResult(errors=[e], invalid=True)  | 
 | 178 | + | 
 | 179 | +    if allow_only_query:  | 
 | 180 | +        operation_ast = get_operation_ast(ast, params.operation_name)  | 
 | 181 | +        if operation_ast and operation_ast.operation != 'query':  | 
 | 182 | +            raise HttpQueryError(  | 
 | 183 | +                405,  | 
 | 184 | +                'Can only perform a {} operation from a POST request.'.format(operation_ast.operation),  | 
 | 185 | +                headers={  | 
 | 186 | +                    'Allow': ['POST'],  | 
 | 187 | +                }  | 
 | 188 | +            )  | 
 | 189 | + | 
 | 190 | +    try:  | 
 | 191 | +        return execute(  | 
 | 192 | +            schema,  | 
 | 193 | +            ast,  | 
 | 194 | +            operation_name=params.operation_name,  | 
 | 195 | +            variable_values=params.variables,  | 
 | 196 | +            **kwargs  | 
 | 197 | +        )  | 
 | 198 | + | 
 | 199 | +    except Exception as e:  | 
 | 200 | +        return ExecutionResult(errors=[e], invalid=True)  | 
 | 201 | + | 
 | 202 | + | 
 | 203 | +def load_json_body(data):  | 
 | 204 | +    try:  | 
 | 205 | +        return json.loads(data)  | 
 | 206 | +    except:  | 
 | 207 | +        raise HttpQueryError(  | 
 | 208 | +            400,  | 
 | 209 | +            'POST body sent invalid JSON.'  | 
 | 210 | +        )  | 
0 commit comments