From 1180d6bf76f19d4b9e6ab93a6add3dcffbcd0725 Mon Sep 17 00:00:00 2001 From: Josue Nina Date: Fri, 13 Mar 2026 18:07:33 -0500 Subject: [PATCH 1/2] Add force refresh of modules JSON on --update flag --- lean/click.py | 3 +++ lean/components/util/update_manager.py | 17 +++++++++++++ tests/components/util/test_update_manager.py | 25 ++++++++++++++++++++ 3 files changed, 45 insertions(+) diff --git a/lean/click.py b/lean/click.py index 0eb4e86d..5f42de81 100644 --- a/lean/click.py +++ b/lean/click.py @@ -274,6 +274,9 @@ def invoke(self, ctx: Context): update_manager = container.update_manager update_manager.show_announcements() + if ctx.params.get("update", False): + update_manager.force_refresh_modules_json() + result = super().invoke(ctx) update_manager.warn_if_cli_outdated() diff --git a/lean/components/util/update_manager.py b/lean/components/util/update_manager.py index e8411b38..ef6e6e80 100644 --- a/lean/components/util/update_manager.py +++ b/lean/components/util/update_manager.py @@ -162,6 +162,23 @@ def show_announcements(self) -> None: self._logger.info(Panel.fit(table, title="Announcements", box=box.SQUARE)) + def force_refresh_modules_json(self) -> None: + """Forces a re-download of the modules JSON file, bypassing the 24-hour cache interval.""" + from lean.models import url as modules_url, file_path as modules_file_path + from requests import get + try: + res = get(modules_url, timeout=5) + if res.ok: + from json import dump + modules_file_path.parent.mkdir(parents=True, exist_ok=True) + with open(modules_file_path, 'w', encoding='utf-8') as f: + dump(res.json(), f, ensure_ascii=False, indent=4) + else: + res.raise_for_status() + except Exception: + # No need to do anything if file isn't available + pass + def _should_check_for_updates(self, key: str, interval_hours: int) -> bool: """Returns whether an update check should be performed. diff --git a/tests/components/util/test_update_manager.py b/tests/components/util/test_update_manager.py index 4144c476..55d7a0f4 100644 --- a/tests/components/util/test_update_manager.py +++ b/tests/components/util/test_update_manager.py @@ -277,6 +277,31 @@ def test_show_announcements_does_nothing_when_latest_announcements_shown_before( logger.info.assert_not_called() +def test_force_refresh_modules_json_downloads_file(requests_mock: RequestsMock) -> None: + from lean.models import url as modules_url, file_path as modules_file_path + modules_json = {"modules": {"SomeModule": {"version": "1.0"}}} + requests_mock.add(requests_mock.GET, modules_url, json=modules_json) + + logger, storage, docker_manager, update_manager = create_objects() + + update_manager.force_refresh_modules_json() + + assert modules_file_path.is_file() + with open(modules_file_path) as f: + assert json.load(f) == modules_json + + +def test_force_refresh_modules_json_does_nothing_when_offline(requests_mock: RequestsMock) -> None: + from lean.models import url as modules_url + from requests.exceptions import ConnectionError as RequestsConnectionError + requests_mock.add(requests_mock.GET, modules_url, body=RequestsConnectionError()) + + logger, storage, docker_manager, update_manager = create_objects() + + # Should not raise + update_manager.force_refresh_modules_json() + + def test_show_announcements_does_nothing_when_there_are_no_announcements(requests_mock: RequestsMock) -> None: requests_mock.add(requests_mock.GET, "https://raw.githubusercontent.com/QuantConnect/lean-cli/master/announcements.json", From 2f9d963db8570140692023dd523e1f86a5666c38 Mon Sep 17 00:00:00 2001 From: Josue Nina Date: Wed, 18 Mar 2026 09:40:52 -0500 Subject: [PATCH 2/2] Move modules JSON download to reusable function --- lean/components/util/update_manager.py | 12 ++---------- lean/models/__init__.py | 27 +++++++++++++++----------- 2 files changed, 18 insertions(+), 21 deletions(-) diff --git a/lean/components/util/update_manager.py b/lean/components/util/update_manager.py index ef6e6e80..0848162b 100644 --- a/lean/components/util/update_manager.py +++ b/lean/components/util/update_manager.py @@ -164,17 +164,9 @@ def show_announcements(self) -> None: def force_refresh_modules_json(self) -> None: """Forces a re-download of the modules JSON file, bypassing the 24-hour cache interval.""" - from lean.models import url as modules_url, file_path as modules_file_path - from requests import get + from lean.models import _download_modules_json try: - res = get(modules_url, timeout=5) - if res.ok: - from json import dump - modules_file_path.parent.mkdir(parents=True, exist_ok=True) - with open(modules_file_path, 'w', encoding='utf-8') as f: - dump(res.json(), f, ensure_ascii=False, indent=4) - else: - res.raise_for_status() + _download_modules_json() except Exception: # No need to do anything if file isn't available pass diff --git a/lean/models/__init__.py b/lean/models/__init__.py index 4e0e8bca..90f33478 100644 --- a/lean/models/__init__.py +++ b/lean/models/__init__.py @@ -23,21 +23,26 @@ # check if new file is available online url = f"https://cdn.quantconnect.com/cli/{file_name}" + + +def _download_modules_json() -> None: + """Downloads the modules JSON file from the CDN and saves it to disk.""" + from requests import get + from json import dump + res = get(url, timeout=5) + if res.ok: + file_path.parent.mkdir(parents=True, exist_ok=True) + with open(file_path, 'w', encoding='utf-8') as f: + dump(res.json(), f, ensure_ascii=False, indent=4) + else: + res.raise_for_status() + + error = None try: # fetch if file not available or fetched before 1 day if not path.exists(file_path) or (time() - path.getmtime(file_path) > 86400): - from requests import get - res = get(url, timeout=5) - if res.ok: - new_content = res.json() - from json import dump - # create parents if not exists - file_path.parent.mkdir(parents=True, exist_ok=True) - with open(file_path, 'w', encoding='utf-8') as f: - dump(new_content, f, ensure_ascii=False, indent=4) - else: - res.raise_for_status() + _download_modules_json() except Exception as e: # No need to do anything if file isn't available error = str(e)