Skip to content

Commit b89f9dd

Browse files
author
Tjaz Erzen
committed
Handle connection error gracefully
1 parent e843825 commit b89f9dd

4 files changed

Lines changed: 38 additions & 9 deletions

File tree

codeplain_REST_api.py

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,14 @@
22
from typing import Optional
33

44
import requests
5+
from requests.exceptions import ConnectionError, Timeout
56

67
import plain2code_exceptions
78
from plain2code_state import RunState
89

910
MAX_RETRIES = 4
1011
RETRY_DELAY = 3
1112

12-
# TODO: Handle connection errors
1313
RETRY_ERROR_CODES = [
1414
"LLMInternalError",
1515
]
@@ -76,7 +76,23 @@ def post_request(self, endpoint_url, headers, payload, run_state: Optional[RunSt
7676
response.raise_for_status()
7777
return response_json
7878

79+
except (ConnectionError, Timeout) as e:
80+
# Network-related errors should always be retried
81+
if attempt < MAX_RETRIES:
82+
self.console.info(f"Network error on attempt {attempt + 1}/{MAX_RETRIES + 1}: {e}")
83+
self.console.info(f"Retrying in {retry_delay} seconds...")
84+
time.sleep(retry_delay)
85+
# Exponential backoff
86+
retry_delay *= 2
87+
else:
88+
self.console.error(f"Max retries ({MAX_RETRIES}) exceeded. Network connection failed.")
89+
self.console.error(f"Last error: {str(e)}")
90+
raise plain2code_exceptions.NetworkConnectionError(
91+
f"Failed to connect to API server after {MAX_RETRIES + 1} attempts. "
92+
)
93+
7994
except Exception as e:
95+
# For other errors, check if they should be retried
8096
if response_json is not None and "error_code" in response_json:
8197
if response_json["error_code"] not in RETRY_ERROR_CODES:
8298
raise e

plain2code.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
MissingAPIKey,
2727
MissingPreviousFunctionalitiesError,
2828
MissingResource,
29+
NetworkConnectionError,
2930
PlainSyntaxError,
3031
UnexpectedState,
3132
)
@@ -288,6 +289,11 @@ def main(): # noqa: C901
288289
exc_info = sys.exc_info()
289290
console.error(f"Missing resource: {str(e)}\n")
290291
console.debug(f"Render ID: {run_state.render_id}")
292+
except NetworkConnectionError as e:
293+
exc_info = sys.exc_info()
294+
console.error(f"Connection error: {str(e)}\n")
295+
console.error("Please check that your internet connection is working.")
296+
console.debug(f"Render ID: {run_state.render_id}")
291297
except Exception as e:
292298
exc_info = sys.exc_info()
293299
console.error(f"Error rendering plain code: {str(e)}\n")

plain2code_exceptions.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,3 +59,9 @@ class MissingPreviousFunctionalitiesError(Exception):
5959
"""Raised when trying to render from a FRID but previous FRID commits are missing."""
6060

6161
pass
62+
63+
64+
class NetworkConnectionError(Exception):
65+
"""Raised when there is a network connectivity issue with the API server."""
66+
67+
pass

tui/plain2code_tui.py

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919
RenderModuleStarted,
2020
RenderStateUpdated,
2121
)
22-
from plain2code_exceptions import InternalServerError
22+
from plain2code_exceptions import InternalServerError, NetworkConnectionError
2323
from render_machine.states import States
2424
from tui.widget_helpers import log_to_widget
2525

@@ -137,13 +137,14 @@ def on_worker_state_changed(self, event: Worker.StateChanged) -> None:
137137
error = event.worker.error
138138
original_error = error.__cause__ if isinstance(error, WorkerFailed) and error.__cause__ else error
139139

140-
# Every error in worker thread gets converted to InternalServerError so it's handled by the common call to
141-
# action in plain2code.py
142-
internal_error = InternalServerError(str(original_error))
143-
internal_error.__cause__ = original_error
144-
145-
# Exit the TUI and return the wrapped exception
146-
self.exit(result=internal_error)
140+
# Connection-related errors are propagated as-is to show helpful error messages
141+
# All other errors are wrapped in InternalServerError for consistent handling
142+
if isinstance(original_error, NetworkConnectionError):
143+
self.exit(result=original_error)
144+
else:
145+
internal_error = InternalServerError(str(original_error))
146+
internal_error.__cause__ = original_error
147+
self.exit(result=internal_error)
147148

148149
def _handle_exception(self, error: Exception) -> None:
149150
"""Override Textual's exception handler to suppress console tracebacks for worker errors.

0 commit comments

Comments
 (0)