Skip to content

Commit 88b94b8

Browse files
Retry requests to server on connection errors
1 parent 362aa49 commit 88b94b8

File tree

1 file changed

+53
-31
lines changed

1 file changed

+53
-31
lines changed

codeplain_REST_api.py

Lines changed: 53 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,14 @@
1+
import time
12
from typing import Optional
23

34
import requests
5+
from requests.exceptions import ConnectionError, RequestException, Timeout
46

57
from plain2code_state import RunState
68

9+
MAX_RETRIES = 3
10+
RETRY_DELAY = 3
11+
712

813
class FunctionalRequirementTooComplex(Exception):
914
def __init__(self, message, proposed_breakdown=None):
@@ -50,8 +55,9 @@ class MultipleRendersFound(Exception):
5055

5156
class CodeplainAPI:
5257

53-
def __init__(self, api_key):
58+
def __init__(self, api_key, console):
5459
self.api_key = api_key
60+
self.console = console
5561

5662
@property
5763
def api_url(self):
@@ -65,51 +71,67 @@ def _extend_payload_with_run_state(self, payload: dict, run_state: RunState):
6571
run_state.increment_call_count()
6672
payload["render_state"] = run_state.to_dict()
6773

68-
def post_request(self, endpoint_url, headers, payload, run_state: Optional[RunState]):
74+
def post_request(self, endpoint_url, headers, payload, run_state: Optional[RunState]): # noqa: C901
6975
if run_state is not None:
7076
self._extend_payload_with_run_state(payload, run_state)
71-
response = requests.post(endpoint_url, headers=headers, json=payload)
7277

73-
try:
74-
response_json = response.json()
75-
except requests.exceptions.JSONDecodeError as e:
76-
print(f"Failed to decode JSON response: {e}. Response text: {response.text}")
77-
raise
78+
retry_delay = RETRY_DELAY
79+
for attempt in range(MAX_RETRIES + 1):
80+
try:
81+
response = requests.post(endpoint_url, headers=headers, json=payload)
82+
83+
try:
84+
response_json = response.json()
85+
except requests.exceptions.JSONDecodeError as e:
86+
print(f"Failed to decode JSON response: {e}. Response text: {response.text}")
87+
raise
7888

79-
if response.status_code == requests.codes.bad_request and "error_code" in response_json:
80-
if response_json["error_code"] == "FunctionalRequirementTooComplex":
81-
raise FunctionalRequirementTooComplex(response_json["message"], response_json.get("proposed_breakdown"))
89+
if response.status_code == requests.codes.bad_request and "error_code" in response_json:
90+
if response_json["error_code"] == "FunctionalRequirementTooComplex":
91+
raise FunctionalRequirementTooComplex(
92+
response_json["message"], response_json.get("proposed_breakdown")
93+
)
8294

83-
if response_json["error_code"] == "ConflictingRequirements":
84-
raise ConflictingRequirements(response_json["message"])
95+
if response_json["error_code"] == "ConflictingRequirements":
96+
raise ConflictingRequirements(response_json["message"])
8597

86-
if response_json["error_code"] == "CreditBalanceTooLow":
87-
raise CreditBalanceTooLow(response_json["message"])
98+
if response_json["error_code"] == "CreditBalanceTooLow":
99+
raise CreditBalanceTooLow(response_json["message"])
88100

89-
if response_json["error_code"] == "LLMInternalError":
90-
raise LLMInternalError(response_json["message"])
101+
if response_json["error_code"] == "LLMInternalError":
102+
raise LLMInternalError(response_json["message"])
91103

92-
if response_json["error_code"] == "MissingResource":
93-
raise MissingResource(response_json["message"])
104+
if response_json["error_code"] == "MissingResource":
105+
raise MissingResource(response_json["message"])
94106

95-
if response_json["error_code"] == "PlainSyntaxError":
96-
raise PlainSyntaxError(response_json["message"])
107+
if response_json["error_code"] == "PlainSyntaxError":
108+
raise PlainSyntaxError(response_json["message"])
97109

98-
if response_json["error_code"] == "OnlyRelativeLinksAllowed":
99-
raise OnlyRelativeLinksAllowed(response_json["message"])
110+
if response_json["error_code"] == "OnlyRelativeLinksAllowed":
111+
raise OnlyRelativeLinksAllowed(response_json["message"])
100112

101-
if response_json["error_code"] == "LinkMustHaveTextSpecified":
102-
raise LinkMustHaveTextSpecified(response_json["message"])
113+
if response_json["error_code"] == "LinkMustHaveTextSpecified":
114+
raise LinkMustHaveTextSpecified(response_json["message"])
103115

104-
if response_json["error_code"] == "NoRenderFound":
105-
raise NoRenderFound(response_json["message"])
116+
if response_json["error_code"] == "NoRenderFound":
117+
raise NoRenderFound(response_json["message"])
106118

107-
if response_json["error_code"] == "MultipleRendersFound":
108-
raise MultipleRendersFound(response_json["message"])
119+
if response_json["error_code"] == "MultipleRendersFound":
120+
raise MultipleRendersFound(response_json["message"])
109121

110-
response.raise_for_status()
122+
response.raise_for_status()
123+
return response_json
111124

112-
return response_json
125+
except (ConnectionError, Timeout, RequestException) as e:
126+
if attempt < MAX_RETRIES:
127+
self.console.info(f"Connection error on attempt {attempt + 1}/{MAX_RETRIES + 1}: {e}")
128+
self.console.info(f"Retrying in {retry_delay} seconds...")
129+
time.sleep(retry_delay)
130+
# Exponential backoff
131+
retry_delay *= 2
132+
else:
133+
self.console.error(f"Max retries ({MAX_RETRIES}) exceeded. Last error: {e}")
134+
raise type(e)(f"Error requesting to the codeplain API: {e}")
113135

114136
def get_plain_source_tree(self, plain_source, loaded_templates):
115137
"""

0 commit comments

Comments
 (0)