1
+ import time
1
2
from typing import Optional
2
3
3
4
import requests
5
+ from requests .exceptions import ConnectionError , RequestException , Timeout
4
6
5
7
from plain2code_state import RunState
6
8
9
+ MAX_RETRIES = 3
10
+ RETRY_DELAY = 3
11
+
7
12
8
13
class FunctionalRequirementTooComplex (Exception ):
9
14
def __init__ (self , message , proposed_breakdown = None ):
@@ -50,8 +55,9 @@ class MultipleRendersFound(Exception):
50
55
51
56
class CodeplainAPI :
52
57
53
- def __init__ (self , api_key ):
58
+ def __init__ (self , api_key , console ):
54
59
self .api_key = api_key
60
+ self .console = console
55
61
56
62
@property
57
63
def api_url (self ):
@@ -65,51 +71,67 @@ def _extend_payload_with_run_state(self, payload: dict, run_state: RunState):
65
71
run_state .increment_call_count ()
66
72
payload ["render_state" ] = run_state .to_dict ()
67
73
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
69
75
if run_state is not None :
70
76
self ._extend_payload_with_run_state (payload , run_state )
71
- response = requests .post (endpoint_url , headers = headers , json = payload )
72
77
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
78
88
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
+ )
82
94
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" ])
85
97
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" ])
88
100
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" ])
91
103
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" ])
94
106
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" ])
97
109
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" ])
100
112
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" ])
103
115
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" ])
106
118
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" ])
109
121
110
- response .raise_for_status ()
122
+ response .raise_for_status ()
123
+ return response_json
111
124
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 } " )
113
135
114
136
def get_plain_source_tree (self , plain_source , loaded_templates ):
115
137
"""
0 commit comments