Repository: open-spaced-repetition/py-fsrs

Issue:
Use lazy loading for the optimizer module
I'm developing a lightweight flashcard software. During testing, I observed that some dependencies (torch/pandas), which are only used in the optimizer module, take about one second to import. Since the optimizer runs only once a month or less frequently in my software, lazy loading would significantly reduce startup time.

Thanks!


Discussion hints:
MorningstarSunsetZ: I've tested it in my software and it's working well.
Thanks again!
codecov[bot]: ## [Codecov](https://app.codecov.io/gh/open-spaced-repetition/py-fsrs/pull/127?dropdown=coverage&src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=open-spaced-repetition) Report
:white_check_mark: All modified and coverable lines are covered by tests.
:white_check_mark: Project coverage is 98.49%. Comparing base ([`e50db2e`](https://app.codecov.io/gh/open-spaced-repetition/py-fsrs/commit/e50db2ea5243f0ffca8dd3a4d5baca64f29ce20c?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=open-spaced-repetition)) to head ([`867ffce`](https://app.codecov.io/gh/open-spaced-repetition/py-fsrs/commit/867ffce2a58af95889dd13c6ad7aa4540791f1d0?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=open-spaced-repetition)).

<details><summary>Additional details and impacted files</summary>



```diff
@@            Coverage Diff             @@
##             main     #127      +/-   ##
==========================================
+ Coverage   98.48%   98.49%   +0.01%     
==========================================
  Files           5        5              
  Lines         593      597       +4     
==========================================
+ Hits          584      588       +4     
  Misses          9        9              
```
</details>

[:umbrella: View full report in Codecov by Sentry](https://app.codecov.io/gh/open-spaced-repetition/py-fsrs/pull/127?dropdown=coverage&src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=open-spaced-repetition).   
:loudspeaker: Have feedback on the report? [Share it here](https://about.codecov.io/codecov-pr-comment-feedback/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=open-spaced-repetition).
<details><summary> :rocket: New features to boost your workflow: </summary>

- :snowflake: [Test Analytics](https://docs.codecov.com/docs/test-analytics): Detect flaky tests, report on failures, and find test suite problems.
</details>

Solution patch:
diff --git a/fsrs/__init__.py b/fsrs/__init__.py
index 281b3a8..3913861 100644
--- a/fsrs/__init__.py
+++ b/fsrs/__init__.py
@@ -9,6 +9,15 @@ from fsrs.scheduler import Scheduler
 from fsrs.card import Card, State
 from fsrs.review_log import ReviewLog, Rating
 
-from fsrs.optimizer import Optimizer
+
+# lazy load the Optimizer module due to heavy dependencies
+def __getattr__(name: str) -> type:
+    if name == "Optimizer":
+        global Optimizer
+        from fsrs.optimizer import Optimizer
+
+        return Optimizer
+    raise AttributeError
+
 
 __all__ = ["Scheduler", "Card", "Rating", "ReviewLog", "State", "Optimizer"]
diff --git a/pyproject.toml b/pyproject.toml
index ec4c6c1..6eb89c0 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
 
 [project]
 name = "fsrs"
-version = "6.1.0"
+version = "6.1.1"
 description = "Free Spaced Repetition Scheduler"
 readme = "README.md"
 authors = [


Test patch:
diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml
index 5fe847c..9fc58a1 100644
--- a/.github/workflows/test.yml
+++ b/.github/workflows/test.yml
@@ -48,7 +48,9 @@ jobs:
           uv pip install ".[optimizer,dev]"
 
       - name: Test with pytest
-        run: pytest --cov=fsrs --cov-report=xml -n auto
+        run: |
+          pytest --cov=fsrs --cov-report= --cov-append tests/test_basic.py
+          pytest --cov=fsrs --cov-report=xml --cov-append tests/test_optimizer.py -n auto
 
       - name: Upload results to Codecov
         uses: codecov/codecov-action@v5
diff --git a/tests/test_basic.py b/tests/test_basic.py
index 541acdb..a515093 100644
--- a/tests/test_basic.py
+++ b/tests/test_basic.py
@@ -7,6 +7,7 @@ import json
 import pytest
 import random
 from copy import deepcopy
+import sys
 
 
 class TestPyFSRS:
@@ -1041,3 +1042,18 @@ class TestPyFSRS:
 
         assert card.state == State.Review
         assert card.step is None
+
+    def test_Optimizer_lazy_loading(self):
+        assert "fsrs.scheduler" in sys.modules
+        assert "fsrs.card" in sys.modules
+        assert "fsrs.review_log" in sys.modules
+
+        assert "fsrs.optimizer" not in sys.modules
+
+        from fsrs import Optimizer  # noqa: F401 (linter: unused import)
+
+        assert "fsrs.optimizer" in sys.modules
+
+    def test_import_non_existent_module(self):
+        with pytest.raises(ImportError):
+            from fsrs import NotAModule  # noqa: F401 (linter: unused import)