Repository: benchopt/benchopt (python)

Issue:
BUG  benchopt info fails when the safe import context is not named `import_ctx`
benchopt info tells me mne is installed, but it is not. Notice I am running directly from the `benchopt_lasso` environment.

```
(benchopt_lasso) ➜  lasso git:(bench) ✗ benchopt info . -d meg         
Info regarding the benchmark 'lasso'
WARNING: astropy not found, will default to scipy for convolution
----------
# DATASETS
----------
## MEG
✓ available in 'running env'
> requirements:
    conda install -c conda-forge mne
----------
(benchopt_lasso) ➜  lasso git:(bench) ✗ ipython
Python 3.9.12 | packaged by conda-forge | (main, Mar 24 2022, 23:22:55) 
Type 'copyright', 'credits' or 'license' for more information
IPython 8.3.0 -- An enhanced Interactive Python. Type '?' for help.

In [1]: import mne
---------------------------------------------------------------------------
ModuleNotFoundError                       Traceback (most recent call last)
Input In [1], in <cell line: 1>()
----> 1 import mne

ModuleNotFoundError: No module named 'mne'
```

from https://github.com/benchopt/benchmark_lasso/pull/84 (maybe an issue in my MEG Dataset class ?

@tanglef 

Pull request:
ENH allow any name for import context
`import_ctx` is still the default name so there should be no overhead in general
closes #370 

Patch:
diff --git a/benchopt/utils/dynamic_modules.py b/benchopt/utils/dynamic_modules.py
index 3becd37..d992b71 100644
--- a/benchopt/utils/dynamic_modules.py
+++ b/benchopt/utils/dynamic_modules.py
@@ -2,6 +2,7 @@
 """
 import sys
 import hashlib
+import warnings
 import importlib
 from pathlib import Path
 
@@ -62,9 +63,21 @@ def _load_class_from_module(module_filename, class_name, benchmark_dir):
 
     # Store the info to easily reload the class
     klass._module_filename = module_filename.resolve()
-    klass._import_ctx = getattr(
-            module, 'import_ctx', safe_import_context()
-    )
+
+    klass._import_ctx = getattr(module, 'import_ctx', None)
+    if klass._import_ctx is None:
+        for var_name in dir(module):
+            var = getattr(module, var_name)
+            if isinstance(var, safe_import_context):
+                klass._import_ctx = var
+                warnings.warn(
+                    "Import contexts should preferably be named import_ctx, "
+                    f"got {var_name}.",  UserWarning
+                )
+                break
+        else:
+            klass._import_ctx = safe_import_context()
+
     klass._benchmark_dir = benchmark_dir.resolve()
     return klass
 


Test patch:
diff --git a/benchopt/tests/test_cli.py b/benchopt/tests/test_cli.py
index ddda597..5de09c8 100644
--- a/benchopt/tests/test_cli.py
+++ b/benchopt/tests/test_cli.py
@@ -1,6 +1,7 @@
 import re
 import time
 import tarfile
+import inspect
 import tempfile
 from pathlib import Path
 
@@ -12,6 +13,7 @@ from click.shell_completion import ShellComplete
 from benchopt.benchmark import Benchmark
 from benchopt.plotting import PLOT_KINDS
 from benchopt.utils.stream_redirection import SuppressStd
+from benchopt.utils.dynamic_modules import _load_class_from_module
 
 
 from benchopt.tests import SELECT_ONE_PGD
@@ -302,6 +304,31 @@ class TestRunCmd:
             run, [str(DUMMY_BENCHMARK_PATH), '-d'], DATASET_COMPLETION_CASES
         )
 
+    def test_import_ctx_name(self):
+        solver = inspect.cleandoc("""
+            from benchopt import BaseSolver, safe_import_context
+            with safe_import_context() as import_ctx_wrong_name:
+                import numpy as np
+
+
+            class Solver(BaseSolver):
+                name = "test_import_ctx"
+
+            """)
+        with tempfile.NamedTemporaryFile(
+                dir=DUMMY_BENCHMARK_PATH / "solvers",
+                mode='w', suffix='.py') as f:
+            f.write(solver)
+            f.flush()
+
+            err_msg = ("Import contexts should preferably be named import_ctx,"
+                       " got import_ctx_wrong_name.")
+            with pytest.warns(UserWarning, match=err_msg):
+                _load_class_from_module(
+                    f.name, "Solver",
+                    benchmark_dir=DUMMY_BENCHMARK_PATH
+                )
+
 
 class TestInstallCmd: