From 943ca0717a6a9f29cf2347290a1fbb32972b9c16 Mon Sep 17 00:00:00 2001 From: YuF-9468 <264538812+YuF-9468@users.noreply.github.com> Date: Tue, 10 Mar 2026 06:38:29 +0800 Subject: [PATCH] Avoid eager FTP connection in SINAN import --- pysus/online_data/SINAN.py | 10 ++++-- pysus/tests/test_sinan_online_data.py | 45 +++++++++++++++++++++++++++ 2 files changed, 53 insertions(+), 2 deletions(-) create mode 100644 pysus/tests/test_sinan_online_data.py diff --git a/pysus/online_data/SINAN.py b/pysus/online_data/SINAN.py index fe5692d..76aeb5f 100644 --- a/pysus/online_data/SINAN.py +++ b/pysus/online_data/SINAN.py @@ -1,3 +1,4 @@ +from functools import lru_cache from pathlib import Path from typing import Union @@ -5,12 +6,15 @@ from pysus.ftp import CACHEPATH from pysus.ftp.databases.sinan import SINAN -sinan = SINAN().load() + +@lru_cache(maxsize=1) +def _get_sinan() -> SINAN: + return SINAN().load() def list_diseases() -> dict: """List available diseases on SINAN""" - return sinan.diseases + return _get_sinan().diseases def get_available_years(disease_code: str) -> list: @@ -21,6 +25,7 @@ def get_available_years(disease_code: str) -> list: :return: A list of DBC files from a specific disease found in the FTP Server. """ + sinan = _get_sinan() files = sinan.get_files(dis_code=disease_code) return sorted(list(set(sinan.describe(f)["year"] for f in files))) @@ -37,6 +42,7 @@ def download( :param data_path: The directory where the file will be downloaded to. :return: list of downloaded files. """ + sinan = _get_sinan() files = sinan.get_files(dis_code=diseases, year=years) return sinan.download(files, local_dir=data_path) diff --git a/pysus/tests/test_sinan_online_data.py b/pysus/tests/test_sinan_online_data.py new file mode 100644 index 0000000..dfb5d81 --- /dev/null +++ b/pysus/tests/test_sinan_online_data.py @@ -0,0 +1,45 @@ +import importlib +import sys +import unittest +from unittest.mock import Mock, patch + + +class TestSINANOnlineDataImport(unittest.TestCase): + MODULE = "pysus.online_data.SINAN" + + def tearDown(self): + sys.modules.pop(self.MODULE, None) + + def test_import_does_not_connect_to_ftp(self): + sys.modules.pop(self.MODULE, None) + + with patch( + "pysus.ftp.databases.sinan.SINAN.load", + side_effect=AssertionError("load() should not run on import"), + ): + module = importlib.import_module(self.MODULE) + + self.assertTrue(hasattr(module, "_get_sinan")) + + def test_sinan_connection_is_loaded_lazily_and_cached(self): + sys.modules.pop(self.MODULE, None) + fake_sinan = Mock() + fake_sinan.diseases = {"DENG": "Dengue"} + fake_sinan.get_files.return_value = ["f1", "f2"] + fake_sinan.describe.side_effect = [ + {"year": "2024"}, + {"year": "2023"}, + ] + + with patch( + "pysus.ftp.databases.sinan.SINAN.load", return_value=fake_sinan + ) as mock_load: + module = importlib.import_module(self.MODULE) + + self.assertEqual(module.list_diseases(), {"DENG": "Dengue"}) + self.assertEqual(module.get_available_years("DENG"), ["2023", "2024"]) + mock_load.assert_called_once() + + +if __name__ == "__main__": + unittest.main()