Coverage for graphqler / fuzzer / engine / retrier / retrier.py: 82%
33 statements
« prev ^ index » next coverage.py v7.13.4, created at 2026-03-20 10:09 -0400
« prev ^ index » next coverage.py v7.13.4, created at 2026-03-20 10:09 -0400
1"""Retrier
2This moule will retry any immediate errors that arise during the query. This is not responsible for running the same query / mutation again,
3rather, it's responsible for modifying the query / mutation to make it work. Scenarios:
4- We have a NON-NULL column that is selected for in the output, but the server is responding with NULL, you will get the following error:
5 {'message': 'Cannot return null for non-nullable field Transaction.payer.'}.
6 In this scenario, we will need to remove the payer key from the mutation / query output fields
7"""
9from graphqler.fuzzer.engine.retrier.utils import find_block_end, remove_lines_within_range
10from graphqler.utils import plugins_handler
11import logging
14class Retrier:
15 def __init__(self, logger: logging.Logger):
16 self.logger = logger.getChild(__name__)
17 self.max_retries = 3
19 def retry(self, url: str, payload: str | dict | list, gql_response: dict, retry_count) -> tuple[dict, bool]:
20 """Retries the payload based on the error
22 Args:
23 url (str): The url of the endpoint
24 payload (str): The payload (either a query or mutation)
25 gql_response (dict): The GraphQL response containing the error
26 retry_count (int): The number of times we've retried
28 Returns:
29 tuple[dict, bool]: The response, and whether the retry succeeded or not
30 """
31 error = gql_response["errors"][0]
33 if isinstance(payload, dict) or isinstance(payload, list):
34 return (gql_response, False)
36 # If the error doesn't have a message, we can't do anything to fix it
37 if "message" not in error:
38 return (gql_response, False)
40 if "Cannot return null for non-nullable field" in error["message"] or "Field must have selections" in error["message"]:
41 if "locations" not in error:
42 return (gql_response, False)
43 locations = error["locations"]
44 for location in locations:
45 payload = self.get_new_payload_for_retry_non_null(payload, location)
46 self.logger.debug(f"Retrying with new payload:\n {payload}")
47 gql_response, request_response = plugins_handler.get_request_utils().send_graphql_request(url, payload)
48 self.logger.info(f"Response: {gql_response}")
49 if "errors" in gql_response:
50 if retry_count < self.max_retries:
51 return self.retry(url, payload, gql_response, retry_count + 1)
52 else:
53 return (gql_response, False)
54 else:
55 return (gql_response, True)
56 else:
57 return (gql_response, False)
59 def get_new_payload_for_retry_non_null(self, payload: str, location: dict) -> str:
60 """Gets a new payload from the original payload, and the location of the error
62 Args:
63 payload (str): The payload
64 error (dict): The error
66 Returns:
67 str: A string of the new payload
68 """
69 line_number = location["line"]
70 block_end = find_block_end(payload, line_number - 1)
71 new_payload = remove_lines_within_range(payload, line_number - 1, block_end)
72 return new_payload