Skip to content

Commit ef79369

Browse files
awaelchlipre-commit-ci[bot]Borda
committed
ENG-1524: Change retry mechansim default in LightningClient (#15412)
* Change retry mechansim default in LightningClient * add changelog * fix weird patching mechanism * remove unused import * hack the system * try again, maybe something has changed in the package Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Jirka Borovec <[email protected]> Co-authored-by: Jirka <[email protected]> Co-authored-by: Jirka Borovec <[email protected]> (cherry picked from commit 7decc50)
1 parent 44b8f24 commit ef79369

File tree

7 files changed

+50
-36
lines changed

7 files changed

+50
-36
lines changed

src/lightning_app/CHANGELOG.md

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,16 +4,17 @@ All notable changes to this project will be documented in this file.
44

55
The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/).
66

7-
87
## [unreleased] - 202Y-MM-DD
98

109

1110
### Added
1211

1312
- Added the `start` method to the work ([#15523](https://github.com/Lightning-AI/lightning/pull/15523))
1413

14+
1515
- Added a `MultiNode` Component to run with distributed computation with any frameworks ([#15524](https://github.com/Lightning-AI/lightning/pull/15524))
1616

17+
1718
- Expose `RunWorkExecutor` to the work and provides default ones for the `MultiNode` Component ([#15561](https://github.com/Lightning-AI/lightning/pull/15561))
1819

1920
- Added a `start_with_flow` flag to the `LightningWork` which can be disabled to prevent the work from starting at the same time as the flow ([#15591](https://github.com/Lightning-AI/lightning/pull/15591))
@@ -34,7 +35,6 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/).
3435

3536
-
3637

37-
-
3838

3939
-
4040

@@ -43,7 +43,6 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/).
4343

4444
-
4545

46-
-
4746

4847
-
4948

@@ -53,11 +52,15 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/).
5352

5453
- Fixed writing app name and id in connect.txt file for the command CLI ([#15443](https://github.com/Lightning-AI/lightning/pull/15443))
5554

55+
5656
- Fixed missing root flow among the flows of the app ([#15531](https://github.com/Lightning-AI/lightning/pull/15531))
5757

58+
5859
- Fixed bug with Multi Node Component and add some examples ([#15557](https://github.com/Lightning-AI/lightning/pull/15557))
5960

6061

62+
- Fixed an issue with the `lightning` CLI taking a long time to error out when the cloud is not reachable ([#15412](https://github.com/Lightning-AI/lightning/pull/15412))
63+
6164

6265
## [1.8.0] - 2022-11-01
6366

src/lightning_app/runners/backends/cloud.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
class CloudBackend(Backend):
1212
def __init__(self, entrypoint_file, queue_id: Optional[str] = None, status_update_interval: int = None):
1313
super().__init__(entrypoint_file, queues=QueuingSystem.MULTIPROCESS, queue_id=queue_id)
14-
self.client = LightningClient()
14+
self.client = LightningClient(retry=True)
1515

1616
def create_work(self, app: "lightning_app.LightningApp", work: "lightning_app.LightningWork") -> None:
1717
raise NotImplementedError

src/lightning_app/utilities/cloud.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ def _get_project(client: LightningClient, project_id: str = LIGHTNING_CLOUD_PROJ
1818
break
1919
else:
2020
raise ValueError(
21-
"Environment variable LIGHTNING_CLOUD_PROJECT_ID is set " "but could not find an associated project."
21+
"Environment variable `LIGHTNING_CLOUD_PROJECT_ID` is set but could not find an associated project."
2222
)
2323
return membership
2424

src/lightning_app/utilities/network.py

Lines changed: 14 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -104,28 +104,26 @@ def wrapped(*args: Any, **kwargs: Any) -> Any:
104104
return wrapped
105105

106106

107-
class _MethodsRetryWrapperMeta(type):
108-
"""This wrapper metaclass iterates through all methods of the type and all bases of it to wrap them into the
109-
:func:`_retry_wrapper`. It applies to all bound callables except the ``__init__`` method.
110-
"""
111-
112-
def __new__(mcs, name, bases, dct):
113-
new_class = super().__new__(mcs, name, bases, dct)
114-
for base in new_class.__mro__[1:-1]:
115-
for key, value in base.__dict__.items():
116-
if callable(value) and value.__name__ != "__init__":
117-
setattr(new_class, key, _retry_wrapper(value))
118-
return new_class
119-
120-
121-
class LightningClient(GridRestClient, metaclass=_MethodsRetryWrapperMeta):
107+
class LightningClient(GridRestClient):
122108
"""The LightningClient is a wrapper around the GridRestClient.
123109
124110
It wraps all methods to monitor connection exceptions and employs a retry strategy.
111+
112+
Args:
113+
retry: Whether API calls should follow a retry mechanism with exponential backoff.
125114
"""
126115

127-
def __init__(self) -> None:
116+
def __new__(cls, *args: Any, **kwargs: Any) -> "LightningClient":
117+
if kwargs.get("retry", False):
118+
for base_class in GridRestClient.__mro__:
119+
for name, attribute in base_class.__dict__.items():
120+
if callable(attribute) and attribute.__name__ != "__init__":
121+
setattr(cls, name, _retry_wrapper(attribute))
122+
return super().__new__(cls)
123+
124+
def __init__(self, retry: bool = False) -> None:
128125
super().__init__(api_client=create_swagger_client())
126+
self._retry = retry
129127

130128

131129
class CustomRetryAdapter(HTTPAdapter):

tests/tests_app/cli/test_cloud_cli.py

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -37,9 +37,6 @@ class FakeResponse:
3737

3838

3939
class FakeLightningClient:
40-
def __init__(self, response, api_client=None):
41-
self._response = response
42-
4340
def lightningapp_instance_service_list_lightningapp_instances(self, *args, **kwargs):
4441
return V1ListLightningappInstancesResponse(lightningapps=[])
4542

@@ -102,7 +99,7 @@ class ExceptionResponse:
10299

103100
class FakeLightningClientCreate(FakeLightningClient):
104101
def __init__(self, *args, create_response, **kwargs):
105-
super().__init__(*args, **kwargs)
102+
super().__init__()
106103
self.create_response = create_response
107104

108105
def lightningapp_v2_service_list_lightningapps_v2(self, *args, **kwargs):
@@ -129,7 +126,7 @@ def test_start_app(create_response, monkeypatch):
129126
monkeypatch.setattr(
130127
cloud_backend,
131128
"LightningClient",
132-
partial(FakeLightningClientCreate, response=FakeResponse(), create_response=create_response),
129+
partial(FakeLightningClientCreate, create_response=create_response),
133130
)
134131
monkeypatch.setattr(cloud, "LocalSourceCodeDir", MagicMock())
135132
monkeypatch.setattr(cloud, "_prepare_lightning_wheels_and_requirements", MagicMock())
@@ -182,8 +179,8 @@ def getheaders(self):
182179

183180

184181
class FakeLightningClientException(FakeLightningClient):
185-
def __init__(self, *args, message, api_client=None, **kwargs):
186-
super().__init__(*args, api_client=api_client, **kwargs)
182+
def __init__(self, *args, message, **kwargs):
183+
super().__init__()
187184
self.message = message
188185

189186
def lightningapp_v2_service_list_lightningapps_v2(self, *args, **kwargs):
@@ -216,7 +213,7 @@ def test_start_app_exception(message, monkeypatch, caplog):
216213

217214
runner = CliRunner()
218215

219-
fake_grid_rest_client = partial(FakeLightningClientException, response=FakeResponse(), message=message)
216+
fake_grid_rest_client = partial(FakeLightningClientException, message=message)
220217
with caplog.at_level(logging.ERROR):
221218
with mock.patch("lightning_app.runners.backends.cloud.LightningClient", fake_grid_rest_client):
222219
result = runner.invoke(run_app, [_FILE_PATH, "--cloud", "--open-ui=False"], catch_exceptions=False)
Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,17 @@
1-
from lightning_app.utilities.network import find_free_network_port
1+
from unittest.mock import patch
2+
3+
from lightning_app.utilities.network import find_free_network_port, LightningClient
24

35

46
def test_port():
57
assert find_free_network_port()
8+
9+
10+
def test_lightning_client_retry_enabled():
11+
with patch("lightning_app.utilities.network._retry_wrapper") as wrapper:
12+
LightningClient() # default: retry=False
13+
wrapper.assert_not_called()
14+
15+
with patch("lightning_app.utilities.network._retry_wrapper") as wrapper:
16+
LightningClient(retry=True)
17+
wrapper.assert_called()

tests/tests_app/utilities/test_secrets.py

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import pytest
66
from lightning_cloud.openapi import V1ListMembershipsResponse, V1ListSecretsResponse, V1Membership, V1Secret
77

8+
import lightning_app
89
from lightning_app.utilities.secrets import _names_to_ids
910

1011

@@ -29,18 +30,21 @@
2930
],
3031
)
3132
@mock.patch("lightning_cloud.login.Auth.authenticate", MagicMock())
32-
@mock.patch("lightning_app.utilities.network.LightningClient.secret_service_list_secrets")
33-
@mock.patch("lightning_app.utilities.network.LightningClient.projects_service_list_memberships")
3433
def test_names_to_ids(
35-
list_memberships: MagicMock,
36-
list_secrets: MagicMock,
3734
secret_names: List[str],
3835
secrets: List[V1Secret],
3936
expected: Dict[str, str],
4037
expected_exception: bool,
38+
monkeypatch,
4139
):
42-
list_memberships.return_value = V1ListMembershipsResponse(memberships=[V1Membership(project_id="default-project")])
43-
list_secrets.return_value = V1ListSecretsResponse(secrets=secrets)
40+
class FakeLightningClient:
41+
def projects_service_list_memberships(self, *_, **__):
42+
return V1ListMembershipsResponse(memberships=[V1Membership(project_id="default-project")])
43+
44+
def secret_service_list_secrets(self, *_, **__):
45+
return V1ListSecretsResponse(secrets=secrets)
46+
47+
monkeypatch.setattr(lightning_app.utilities.secrets, "LightningClient", FakeLightningClient)
4448

4549
if expected_exception:
4650
with pytest.raises(ValueError):

0 commit comments

Comments
 (0)