async def _observe_subscription(self, asyncgen: AsyncGenerator,
                                    operation_id: str,
                                    websocket: WebSocket) -> None:
        try:
            async for result in asyncgen:
                payload = {}
                if result.data:
                    payload["data"] = result.data
                if result.errors:
                    payload["errors"] = [
                        format_error(error) for error in result.errors
                    ]
                await websocket.send_json({
                    "type": GQL_DATA,
                    "id": operation_id,
                    "payload": payload
                })
        except Exception as error:
            if not isinstance(error, GraphQLError):
                error = GraphQLError(str(error), original_error=error)
            await websocket.send_json({
                "type": GQL_DATA,
                "id": operation_id,
                "payload": {
                    "errors": [format_error(error)]
                },
            })

        if (websocket.client_state != WebSocketState.DISCONNECTED and
                websocket.application_state != WebSocketState.DISCONNECTED):
            await websocket.send_json({
                "type": GQL_COMPLETE,
                "id": operation_id
            })
def quiz_model_query(client,
                     model_query_function,
                     result_name,
                     variables,
                     expect_length=1):
    """
        Tests a query for a model with variables that produce exactly one result
    :param client: Apollo client
    :param model_query_function: Query function expecting the client and variables
    :param result_name: The name of the result object in the data object
    :param variables: key value variables for the query
    :param expect_length: Default 1. Optional number items to expect
    :return: returns the result for further assertions
    """
    all_result = model_query_function(client)
    assert not R.has('errors', all_result), R.dump_json(
        R.map(lambda e: format_error(e), R.prop('errors', all_result)))
    result = model_query_function(client, variables=variables)
    # Check against errors
    assert not R.has('errors', result), R.dump_json(
        R.map(lambda e: format_error(e), R.prop('errors', result)))
    # Simple assertion that the query looks good
    assert expect_length == R.length(R.item_path(['data', result_name],
                                                 result))
    return result
Beispiel #3
0
async def db_handler(request):
    """Serve GraphQL queries."""

    payload = await request.json()
    query = payload.get('query')
    variables = payload.get('variables')

    context = _build_context(request)

    response = await tangoschema.execute(query,
                                         variable_values=variables,
                                         context_value=context,
                                         return_promise=True)

    data = {}
    if response.errors:
        if isinstance(response.errors[0].original_error, UserUnauthorizedException):
            return web.HTTPUnauthorized()
        else:
            data['errors'] = [format_error(e) for e in response.errors]
    if response.data:
        data['data'] = response.data
    jsondata = json.dumps(data,)

    return web.Response(text=jsondata,
                        headers={'Content-Type': "application/json"})
Beispiel #4
0
 async def graphql_http_server(self, request: Request) -> Response:
     try:
         query, variables, operation_name = await self.extract_data_from_request(
             request)
         document = parse(query)
         result = await graphql(
             self.schema,
             query,
             root_value=await
             self.root_value_for_document(document, variables),
             context_value=await self.context_for_request(request),
             variable_values=variables,
             operation_name=operation_name,
         )
     except GraphQLError as error:
         response = {"errors": [{"message": error.message}]}
         return JSONResponse(response)
     except HttpError as error:
         response = error.message or error.status
         return Response(response, status_code=400)
     else:
         response = {"data": result.data}
         if result.errors:
             response["errors"] = [format_error(e) for e in result.errors]
         return JSONResponse(response)
Beispiel #5
0
 def execution_result_to_dict(self, execution_result):
     result = OrderedDict()
     if execution_result.data:
         result['data'] = execution_result.data
     if execution_result.errors:
         result['errors'] = [format_error(error)
                             for error in execution_result.errors]
     return result
Beispiel #6
0
 async def send_execution_result(self, context: ConnectionContext, op_id: str, result: ExecutionResult) -> None:
     payload = {
         'data': result.data,
         'errors': [format_error(error) for error in result.errors] if result.errors else None,
     }
     await self.send_message(
         context, MessageType.GQL_DATA, op_id=op_id, payload=payload,
     )
 async def send_result(self, operation_id: str,
                       result: ExecutionResult) -> None:
     payload = {}
     if result.data:
         payload["data"] = result.data
     if result.errors:
         payload["errors"] = [format_error(e) for e in result.errors]
     await self.send_message(operation_id, "data", payload)
Beispiel #8
0
 def execution_result_to_dict(self, execution_result):
     result = OrderedDict()
     if execution_result.data:
         result['data'] = execution_result.data
     if execution_result.errors:
         result['errors'] = [format_error(error)
                             for error in execution_result.errors]
     return result
Beispiel #9
0
    def return_response_from_result(self, start_response: Callable,
                                    result: ExecutionResult) -> List[bytes]:
        response = {"data": result.data}
        if result.errors:
            response["errors"] = [format_error(e) for e in result.errors]

        start_response(HTTP_STATUS_200_OK,
                       [("Content-Type", CONTENT_TYPE_JSON)])
        return [json.dumps(response).encode("utf-8")]
Beispiel #10
0
 def execution_result_to_dict(self, execution_result):
     result = {}
     if execution_result.data:
         result["data"] = execution_result.data
     if execution_result.errors:
         result["errors"] = [
             format_error(error) for error in execution_result.errors
         ]
     return result
def quiz_model_mutation_create(client,
                               graphql_update_or_create_function,
                               result_path,
                               values,
                               second_create_results=None,
                               second_create_does_update=False):
    """
        Tests a create mutation for a model
    :param client: The Apollo Client
    :param graphql_update_or_create_function: The update or create mutation function for the model. Expects client and input values
    :param result_path: The path to the result of the create in the data object (e.g. createRegion.region)
    :param values: The input values to use for the create
    :param second_create_results: Object, tests a second create if specified. Use to make sure that create with the same values
    creates a new instance or updates, depending on what you expect it to do.
    The values of this should be regexes that match the created instance
    :param second_create_does_update: Default False. If True expects a second create with the same value to update rather than create a new instance
    :return: Tuple with two return values. The second is null if second_create_results is False
    """
    result = graphql_update_or_create_function(client, values=values)

    result_path_partial = R.item_str_path(f'data.{result_path}')
    assert not R.has('errors', result), R.dump_json(
        R.map(lambda e: format_error(e), R.prop('errors', result)))
    # Get the created value, using underscore to make the camelcase keys match python keys
    created = R.map_keys(lambda key: underscore(key),
                         result_path_partial(result))
    # get all the keys in values that are in created. This should match values if created has everything we expect
    assert values == pick_deep(created, values)
    # Try creating with the same values again, unique constraints will apply to force a create or an update will occur
    if second_create_results:
        new_result = graphql_update_or_create_function(client, values)
        assert not R.has('errors', new_result), R.dump_json(
            R.map(lambda e: format_error(e), R.prop('errors', new_result)))
        created_too = result_path_partial(new_result)
        if second_create_does_update:
            assert created['id'] == created_too['id']
        if not second_create_does_update:
            assert created['id'] != created_too['id']
        for path, value in R.flatten_dct(second_create_results, '.').items():
            assert re.match(value, R.item_str_path_or(None, path, created_too))
    else:
        new_result = None

    return result, new_result
 async def return_response_from_result(self,
                                       result: ExecutionResult) -> None:
     response = {"data": result.data}
     if result.errors:
         response["errors"] = [format_error(e) for e in result.errors]
     await self.send_response(
         200,
         json.dumps(response).encode("utf-8"),
         headers=[(b"Content-Type", CONTENT_TYPE_JSON.encode("utf-8"))],
     )
Beispiel #13
0
async def graphql_view(request):
    payload = await request.json()
    response = await schema.execute(payload.get("query", ""), return_promise=True)
    data = {}
    if response.errors:
        data["errors"] = [format_error(e) for e in response.errors]
    if response.data:
        data["data"] = response.data
    jsondata = json.dumps(data,)
    return web.Response(text=jsondata, headers={"Content-Type": "application/json"})
Beispiel #14
0
    async def _handle_request(self, request: Request) -> Response:
        # route GET requests to the IDE
        if request.method == "GET" and "text/html" in request.headers.get("Accept", ""):
            # read the GraphiQL playground html and serve it as content
            graphiql = pathlib.Path(__file__).parent / "graphiql.html"
            raw_html = None

            with open(graphiql.absolute(), "r") as f:
                raw_html = f.read()

            return HTMLResponse(raw_html)
        # route POST requests to the graphql executor
        elif request.method == "POST":
            content_type = request.headers.get("Content-Type", "")

            # parse graphql according to content type
            if "application/json" in content_type:
                data = await request.json()
            else:
                return PlainTextResponse(
                    "Unsupported Media Type",
                    status_code=status.HTTP_415_UNSUPPORTED_MEDIA_TYPE,
                )
        else:
            return PlainTextResponse(
                "Method Not Allowed", status_code=status.HTTP_405_METHOD_NOT_ALLOWED
            )

        # attempt to pull final query and vars
        try:
            query = data["query"]
            variables = data.get("variables")
        except KeyError:
            return PlainTextResponse(
                "No GraphQL query found in the request",
                status_code=status.HTTP_400_BAD_REQUEST,
            )

        # construct foundation fastapi context
        background = BackgroundTasks()
        context = {"request": request, "background": background}
        result = await self._execute_graphql(query, variables, context)

        # parse graphql result
        response = {}
        if not result.invalid:
            response["data"] = result.data
        if result.errors:
            response["errors"] = [format_error(e) for e in result.errors]

        status_code = (
            status.HTTP_400_BAD_REQUEST if result.errors else status.HTTP_200_OK
        )

        return ORJSONResponse(response, status_code=status_code, background=background)
Beispiel #15
0
    async def send_error_response(self, results, operation_id):
        # set default error message.
        payload = dict(message=dict(
            message="Unhandled error occoured", locations=[], path=""))
        # Set error message if value is available
        if results.errors is not None and len(results.errors) > 0:
            payload = dict(message=graphql.format_error(results.errors[0]))

        json_response: str = json.dumps(
            dict(type=GQLEnum.ERROR.value, id=operation_id, payload=payload))
        await self.websocket.send_text(json_response)
Beispiel #16
0
def test_parse_error(client):
    with pytest.raises(Exception) as exc_info:
        gql("""
            qeury
            """)
    error = exc_info.value
    assert isinstance(error, GraphQLError)
    formatted_error = format_error(error)
    assert formatted_error["locations"] == [{"column": 13, "line": 2}]
    assert formatted_error[
        "message"] == "Syntax Error: Unexpected Name 'qeury'."
Beispiel #17
0
def dump_errors(result):
    """
        Dump any errors to in the result to stderr
    :param result:
    :return:
    """
    if R.has('errors', result):
        for error in result['errors']:
            logger.error(format_error(error))
            if 'stack' in error:
                traceback.print_tb(error['stack'], limit=10, file=sys.stderr)
Beispiel #18
0
def test_parse_error(schema):
    query = """
        qeury
    """
    result = graphql(schema, query)
    assert result.invalid
    formatted_error = format_error(result.errors[0])
    assert formatted_error["locations"] == [{"column": 9, "line": 2}]
    assert ('Syntax Error GraphQL (2:9) Unexpected Name "qeury"'
            in formatted_error["message"])
    assert result.data is None
Beispiel #19
0
async def graphql_view(request):
    payload = await request.json()
    response = await schema.execute(payload.get('query', ''),
                                    return_promise=True)
    data = {}
    if response.errors:
        data['errors'] = [format_error(e) for e in response.errors]
    if response.data:
        data['data'] = response.data
    jsondata = json.dumps(data, )
    return web.Response(text=jsondata,
                        headers={'Content-Type': 'application/json'})
Beispiel #20
0
    def return_response_from_result(self, start_response: Callable,
                                    result: ExecutionResult) -> List[bytes]:
        status = HTTP_STATUS_200_OK
        response = {}
        if result.errors:
            response["errors"] = [format_error(e) for e in result.errors]
        if result.invalid:
            status = HTTP_STATUS_400_BAD_REQUEST
        else:
            response["data"] = result.data

        start_response(status, [("Content-Type", CONTENT_TYPE_JSON)])
        return [json.dumps(response).encode("utf-8")]
Beispiel #21
0
    async def graphql_ws_server(self, websocket: WebSocket) -> None:
        subscriptions: Dict[str, AsyncGenerator] = {}
        await websocket.accept("graphql-ws")
        try:
            while True:
                message = await self.receive_json(websocket)
                operation_id = cast(str, message.get("id"))
                message_type = cast(str, message.get("type"))

                if message_type == GQL_CONNECTION_INIT:
                    await self.send_json(websocket,
                                         {"type": GQL_CONNECTION_ACK})
                elif message_type == GQL_CONNECTION_TERMINATE:
                    break
                elif message_type == GQL_START:
                    query, variables, operation_name = await self.extract_data_from_websocket(
                        message)
                    document = parse(query)
                    results = await subscribe(
                        self.schema,
                        document,
                        root_value=await
                        self.root_value_for_document(document, variables),
                        context_value=await self.context_for_request(message),
                        variable_values=variables,
                        operation_name=operation_name,
                    )
                    if isinstance(results, ExecutionResult):
                        payload = {"message": format_error(results.errors[0])}
                        await self.send_json(
                            websocket,
                            {
                                "type": GQL_ERROR,
                                "id": operation_id,
                                "payload": payload
                            },
                        )
                    else:
                        subscriptions[operation_id] = results
                        asyncio.ensure_future(
                            self.observe_async_results(results, operation_id,
                                                       websocket))
                elif message_type == GQL_STOP:
                    if operation_id in subscriptions:
                        await subscriptions[operation_id].aclose()
                        del subscriptions[operation_id]
        except WebSocketDisconnect:
            for operation_id in subscriptions:
                await subscriptions[operation_id].aclose()
                del subscriptions[operation_id]
def quiz_model_mutation_update(client, graphql_update_or_create_function,
                               create_path, update_path, values,
                               update_values):
    """
        Tests an update mutation for a model by calling a create with the given values then an update
        with the given update_values (plus the create id)
    :param client: The Apollo Client
    :param graphql_update_or_create_function: The update or create mutation function for the model. Expects client and input values
    :param create_path: The path to the result of the create in the data object (e.g. createRegion.region)
    :param update_path: The path to the result of the update in the data object (e.g. updateRegion.region)
    :param values: The input values to use for the create
    :param update_values: The input values to use for the update. This can be as little as one key value
    :return:
    """
    result = graphql_update_or_create_function(client, values=values)
    assert not R.has('errors', result), R.dump_json(
        R.map(lambda e: format_error(e), R.prop('errors', result)))
    # Extract the result and map the graphql keys to match the python keys
    created = R.compose(
        lambda r: R.map_keys(lambda key: underscore(key), r),
        lambda r: R.item_str_path(f'data.{create_path}', r))(result)
    # look at the users added and omit the non-determinant dateJoined
    assert values == pick_deep(created, values)
    # Update with the id and optionally key if there is one + update_values
    update_result = graphql_update_or_create_function(
        client,
        R.merge_all([
            dict(id=created['id']),
            dict(key=created['key'])
            if R.prop_or(False, 'key', created) else {}, update_values
        ]))
    assert not R.has('errors', update_result), R.dump_json(
        R.map(lambda e: format_error(e), R.prop('errors', update_result)))
    updated = R.item_str_path(f'data.{update_path}', update_result)
    assert created['id'] == updated['id']
    assert update_values == pick_deep(update_values, updated)
    return result, update_result
    async def _ws_on_start(
        self,
        data: Any,
        operation_id: str,
        websocket: WebSocket,
        subscriptions: Dict[str, AsyncGenerator],
    ):
        query = data["query"]
        variable_values = data.get("variables")
        operation_name = data.get("operationName")
        context_value = await self._get_context_value(websocket)
        errors: List[GraphQLError] = []
        operation: Optional[OperationDefinitionNode] = None
        document: Optional[DocumentNode] = None

        try:
            document = parse(query)
            operation = get_operation_ast(document, operation_name)
            errors = validate(self.schema.graphql_schema, document)
        except GraphQLError as e:
            errors = [e]

        if operation and operation.operation == OperationType.SUBSCRIPTION:
            errors = await self._start_subscription(
                websocket,
                operation_id,
                subscriptions,
                document,
                context_value,
                variable_values,
                operation_name,
            )
        else:
            errors = await self._handle_query_via_ws(
                websocket,
                operation_id,
                subscriptions,
                document,
                context_value,
                variable_values,
                operation_name,
            )

        if errors:
            await websocket.send_json({
                "type": GQL_ERROR,
                "id": operation_id,
                "payload": format_error(errors[0]),
            })
    async def run(self, query: str,
                  context: Dict[str, Any] = None) -> QueryResult:
        context = context or {}
        graphql_kwargs = context.pop('graphql', {'context_value': {}})
        graphql_context = graphql_kwargs['context_value']
        graphql_context.update({'dataloaders': {}})

        graphql_result = await graphql(self.schema, query, **graphql_kwargs)

        data = graphql_result.data
        errors = []
        for error in graphql_result.errors or []:
            self.logger.error(error, exc_info=error)
            errors.append(format_error(error))

        return QueryResult(data, errors or None)
Beispiel #25
0
 async def send_execution_result(
     self,
     connection_context: AbstractConnectionContext,
     op_id: str,
     execution_result: graphql.ExecutionResult,
 ) -> None:
     result = {}
     if execution_result.data:
         result["data"] = execution_result.data
     if execution_result.errors:
         result["errors"] = [
             graphql.format_error(error) for error in execution_result.errors
         ]
     return await self.send_message(
         connection_context, op_id, GQLMsgType.DATA, result
     )
Beispiel #26
0
    async def stream_channel(self, results, operation_id):
        async for result in results:
            resp = dict()
            if result and result.data:
                resp = dict(type=GQLEnum.DATA.value,
                            id=operation_id,
                            payload=dict(data=result.data))
            if result and result.errors:
                resp = dict(
                    errors=[graphql.format_error(e) for e in result.errors])
            json_response: str = json.dumps(
                dict(type=GQLEnum.DATA.value, id=operation_id, payload=resp))
            await self.websocket.send_text(json_response)

        json_response: dict = json.dumps(
            dict(type=GQLEnum.COMPLETE.value, id=operation_id))
        await self.websocket.send_text(json_response)
Beispiel #27
0
 async def observe_async_results(self, results: AsyncGenerator,
                                 operation_id: str,
                                 websocket: WebSocket) -> None:
     async for result in results:
         payload = {}
         if result.data:
             payload["data"] = result.data
         if result.errors:
             payload["errors"] = [format_error(e) for e in result.errors]
         await self.send_json(websocket, {
             "type": GQL_DATA,
             "id": operation_id,
             "payload": payload
         })
     await self.send_json(websocket, {
         "type": GQL_COMPLETE,
         "id": operation_id
     })
Beispiel #28
0
def dump_errors(result):
    """
        Dump any errors to in the result to stderr
    :param result:
    :return:
    """
    if R.has('errors', result):
        for error in result['errors']:
            logging.exception(traceback)
            if hasattr(error, 'stack'):
                # Syncrounous calls or something
                # See https://github.com/graphql-python/graphql-core/issues/237
                tb = error['stack']
            else:
                # Promises
                tb = error.__traceback__
            formatted_tb = traceback.format_tb(tb)
            error.stack = error.__traceback__
            # This hopefully includes the traceback
            logger.exception(format_error(error))
    async def _handle_query_via_ws(
        self,
        websocket,
        operation_id,
        subscriptions,
        document,
        context_value,
        variable_values,
        operation_name,
    ) -> List[GraphQLError]:
        result2 = execute(
            self.schema.graphql_schema,
            document,
            root_value=self.root_value,
            context_value=context_value,
            variable_values=variable_values,
            operation_name=operation_name,
            middleware=self.middleware,
        )

        if isinstance(result2, ExecutionResult) and result2.errors:
            return result2.errors

        if isawaitable(result2):
            result2 = await cast(Awaitable[ExecutionResult], result2)

        result2 = cast(ExecutionResult, result2)

        payload: Dict[str, Any] = {}
        payload["data"] = result2.data
        if result2.errors:
            payload["errors"] = [
                format_error(error) for error in result2.errors
            ]

        await websocket.send_json({
            "type": GQL_DATA,
            "id": operation_id,
            "payload": payload
        })
        return []
    async def _handle_http_request(self, request: Request) -> JSONResponse:
        try:
            operations = await _get_operation_from_request(request)
        except ValueError as error:
            return JSONResponse({"errors": [error.args[0]]}, status_code=400)

        if isinstance(operations, list):
            return JSONResponse(
                {"errors": ["This server does not support batching"]},
                status_code=400)
        else:
            operation = operations

        query = operation["query"]
        variable_values = operation.get("variables")
        operation_name = operation.get("operationName")
        context_value = await self._get_context_value(request)

        result = await graphql(
            self.schema.graphql_schema,
            source=query,
            context_value=context_value,
            root_value=self.root_value,
            middleware=self.middleware,
            variable_values=variable_values,
            operation_name=operation_name,
        )

        response: Dict[str, Any] = {"data": result.data}
        if result.errors:
            response["errors"] = [
                format_error(error) for error in result.errors
            ]

        status_code = 200 if not result.errors else 400
        return JSONResponse(response, status_code=status_code)
def quiz_model_versioned_query(client, model_class, model_query, result_name,
                               version_count_expected, props, omit_props):
    """
        Tests a versioned query for a model with variables
    :param client: Apollo client
    :param model_class: Model class
    :param model_query: Model's query that should return one result (as a filter)
    number of items in the database that match props
    :param result_name: The name of the results in data.[result_name].objects
    :param version_count_expected The number of versions of the instance we expect
    :param props: The props to query to find a single instance. Should just be {id:...}
    :param omit_props: Props to omit from assertions because they are nondeterminate
    :return:
    """
    result = model_query(
        client,
        variables=dict(objects=R.to_array_if_not(dict(instance=props))))
    # Check against errors
    assert not R.has('errors', result), R.dump_json(
        R.map(lambda e: format_error(e), R.prop('errors', result)))
    assert R.compose(
        R.length, R.item_str_path_or(
            [],
            f'data.{result_name}.objects'))(result) == version_count_expected