From 48a19e24958f561a1b37f35935f545d99a7628bb Mon Sep 17 00:00:00 2001 From: bruAristimunha Date: Mon, 5 May 2025 20:33:27 +0200 Subject: [PATCH 01/14] [EHN] improve the parallel load and set --- mne/utils/config.py | 35 ++++++++++++++++++++++++++++++++--- pyproject.toml | 2 ++ 2 files changed, 34 insertions(+), 3 deletions(-) diff --git a/mne/utils/config.py b/mne/utils/config.py index c28373fcb93..af32f0e3a9b 100644 --- a/mne/utils/config.py +++ b/mne/utils/config.py @@ -7,6 +7,7 @@ import atexit import json import multiprocessing +import contextlib import os import os.path as op import platform @@ -217,17 +218,45 @@ def set_memmap_min_size(memmap_min_size): "MNE_LSL", # mne-lsl ) +@contextlib.contextmanager +def _open_lock(path, *args, **kwargs): + """ + Context manager that opens a file with an optional file lock. + If the `filelock` package is available, a lock is acquired on a lock file + based on the given path (by appending '.lock'). + Otherwise, a null context is used. The file is then opened in the + specified mode. + """ + filelock = _soft_import("filelock", purpose="parallel integration", strict=False) + + if filelock is not None: + lock_path = f"{path}.lock" + try: + lock_context = FileLock(lock_path, timeout=2) + except TimeoutError: + lock_context = contextlib.nullcontext() + else: + lock_context = contextlib.nullcontext() + + with lock_context: + if "hdf5" in str(path): + with h5py.File(path, *args, **kwargs) as fid: + yield fid + else: + with open(path, *args, **kwargs) as fid: + yield fid + def _load_config(config_path, raise_error=False): """Safely load a config file.""" - with open(config_path) as fid: + with _open_lock(config_path) as fid: try: config = json.load(fid) except ValueError: # No JSON object could be decoded --> corrupt file? msg = ( f"The MNE-Python config file ({config_path}) is not a valid JSON " - "file and might be corrupted" + "file and might be corrupted. Consider deleting it and generating again." ) if raise_error: raise RuntimeError(msg) @@ -398,7 +427,7 @@ def set_config(key, value, home_dir=None, set_env=True): directory = op.dirname(config_path) if not op.isdir(directory): os.mkdir(directory) - with open(config_path, "w") as fid: + with _load_config(config_path, "w") as fid: json.dump(config, fid, sort_keys=True, indent=0) diff --git a/pyproject.toml b/pyproject.toml index 59e5a1703e3..9fa723a48dd 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -131,6 +131,7 @@ full-no-qt = [ "trame-vuetify", "vtk >= 9.2", "xlrd", + filelock = ">=3.18.0", ] full-pyqt6 = ["mne[full]"] full-pyside6 = ["mne[full-no-qt]", "PySide6 != 6.7.0, != 6.8.0, != 6.8.0.1"] @@ -172,6 +173,7 @@ test_extra = [ "snirf", "sphinx-gallery", "statsmodels", + " ] [project.urls] From c29fd1db0fb7fd13177eca8d55978b71d24261d7 Mon Sep 17 00:00:00 2001 From: bruAristimunha Date: Mon, 5 May 2025 20:56:58 +0200 Subject: [PATCH 02/14] including the _lock path --- environment.yml | 1 + mne/utils/config.py | 40 ++++++++++++++++++++++++++-------------- pyproject.toml | 3 +-- 3 files changed, 28 insertions(+), 16 deletions(-) diff --git a/environment.yml b/environment.yml index 78c773e56bf..2d0dc698477 100644 --- a/environment.yml +++ b/environment.yml @@ -11,6 +11,7 @@ dependencies: - dipy - edfio >=0.2.1 - eeglabio + - filelock >=3.18.0 - h5io >=0.2.4 - h5py - imageio >=2.6.1 diff --git a/mne/utils/config.py b/mne/utils/config.py index af32f0e3a9b..dac31804137 100644 --- a/mne/utils/config.py +++ b/mne/utils/config.py @@ -5,9 +5,9 @@ # Copyright the MNE-Python contributors. import atexit +import contextlib import json import multiprocessing -import contextlib import os import os.path as op import platform @@ -24,7 +24,13 @@ from packaging.version import parse from ._logging import logger, warn -from .check import _check_fname, _check_option, _check_qt_version, _validate_type +from .check import ( + _check_fname, + _check_option, + _check_qt_version, + _soft_import, + _validate_type, +) from .docs import fill_doc from .misc import _pl @@ -218,20 +224,31 @@ def set_memmap_min_size(memmap_min_size): "MNE_LSL", # mne-lsl ) + @contextlib.contextmanager -def _open_lock(path, *args, **kwargs): +def _path_lock(path): """ Context manager that opens a file with an optional file lock. + If the `filelock` package is available, a lock is acquired on a lock file - based on the given path (by appending '.lock'). - Otherwise, a null context is used. The file is then opened in the + based on the given path (by appending '.lock'). + + Otherwise, a null context is used. The path is then opened in the specified mode. + + Parameters + ---------- + path : str + The path to the file to be opened. + """ filelock = _soft_import("filelock", purpose="parallel integration", strict=False) if filelock is not None: lock_path = f"{path}.lock" try: + from filelock import FileLock + lock_context = FileLock(lock_path, timeout=2) except TimeoutError: lock_context = contextlib.nullcontext() @@ -239,24 +256,19 @@ def _open_lock(path, *args, **kwargs): lock_context = contextlib.nullcontext() with lock_context: - if "hdf5" in str(path): - with h5py.File(path, *args, **kwargs) as fid: - yield fid - else: - with open(path, *args, **kwargs) as fid: - yield fid + yield path def _load_config(config_path, raise_error=False): """Safely load a config file.""" - with _open_lock(config_path) as fid: + with open(_path_lock(config_path)) as fid: try: config = json.load(fid) except ValueError: # No JSON object could be decoded --> corrupt file? msg = ( f"The MNE-Python config file ({config_path}) is not a valid JSON " - "file and might be corrupted. Consider deleting it and generating again." + "file and might be corrupted" ) if raise_error: raise RuntimeError(msg) @@ -427,7 +439,7 @@ def set_config(key, value, home_dir=None, set_env=True): directory = op.dirname(config_path) if not op.isdir(directory): os.mkdir(directory) - with _load_config(config_path, "w") as fid: + with open(_path_lock(config_path), "w") as fid: json.dump(config, fid, sort_keys=True, indent=0) diff --git a/pyproject.toml b/pyproject.toml index 9fa723a48dd..0ca8d6cbf5c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -93,6 +93,7 @@ full-no-qt = [ "dipy", "edfio >= 0.2.1", "eeglabio", + "filelock>=3.18.0", "h5py", "imageio >= 2.6.1", "imageio-ffmpeg >= 0.4.1", @@ -131,7 +132,6 @@ full-no-qt = [ "trame-vuetify", "vtk >= 9.2", "xlrd", - filelock = ">=3.18.0", ] full-pyqt6 = ["mne[full]"] full-pyside6 = ["mne[full-no-qt]", "PySide6 != 6.7.0, != 6.8.0, != 6.8.0.1"] @@ -173,7 +173,6 @@ test_extra = [ "snirf", "sphinx-gallery", "statsmodels", - " ] [project.urls] From 9b9a6402b8e7e9a6ab77493625daa83d15be7c4a Mon Sep 17 00:00:00 2001 From: bruAristimunha Date: Mon, 5 May 2025 22:19:20 +0200 Subject: [PATCH 03/14] Including tests --- doc/changes/names.inc | 1 + mne/utils/tests/test_config.py | 82 ++++++++++++++++++++++++++++++++++ 2 files changed, 83 insertions(+) diff --git a/doc/changes/names.inc b/doc/changes/names.inc index 2e4027b82b7..d20931b7a51 100644 --- a/doc/changes/names.inc +++ b/doc/changes/names.inc @@ -35,6 +35,7 @@ .. _Austin Hurst: https://github.com/a-hurst .. _Ben Beasley: https://github.com/musicinmybrain .. _Britta Westner: https://britta-wstnr.github.io +.. _Bruno Aristimunha: https://bruaristimunha.github.io .. _Bruno Nicenboim: https://bnicenboim.github.io .. _btkcodedev: https://github.com/btkcodedev .. _buildqa: https://github.com/buildqa diff --git a/mne/utils/tests/test_config.py b/mne/utils/tests/test_config.py index 4426eae0fc7..9b1c49b9868 100644 --- a/mne/utils/tests/test_config.py +++ b/mne/utils/tests/test_config.py @@ -2,9 +2,12 @@ # License: BSD-3-Clause # Copyright the MNE-Python contributors. +import json import os import platform +import random import re +import time from functools import partial from pathlib import Path from urllib.error import URLError @@ -232,3 +235,82 @@ def bad_open(url, timeout, msg): out = out.getvalue() assert "devel, " in out assert "updating.html" not in out + + +def _worker_update_config_loop(home_dir, worker_id, iterations=10): + """Util function to update config in parallel. + + Worker function that repeatedly reads the config (via get_config) + and then updates it (via set_config) with a unique key/value pair. + A short random sleep is added to encourage interleaving. + + Dummy function to simulate a worker that reads and updates the config. + + Parameters + ---------- + home_dir : str + The home directory where the config file is located. + worker_id : int + The ID of the worker (for creating unique keys). + iterations : int + The number of iterations to run the loop. + + """ + for i in range(iterations): + # Read current configuration (to simulate a read-modify cycle) + _ = get_config(home_dir=home_dir) + # Create a unique key/value pair. + new_key = f"worker_{worker_id}_{i}" + new_value = f"value_{worker_id}_{i}" + # Update the configuration (our set_config holds the lock over the full cycle) + set_config(new_key, new_value, home_dir=home_dir) + time.sleep(random.uniform(0, 0.05)) + return worker_id + + +def test_parallel_get_set_config(tmp_path): + """Test that uses parallel workers to get and set config. + + All the workers update the same configuration file concurrently. In a + correct implementation with proper path file locking, the final + config file remains valid JSON and includes all expected updates. + + """ + pytest.importorskip("joblib") + pytest.importorskip("FileLock") + from joblib import Parallel, delayed + + # Use the temporary directory as our home directory. + home_dir = str(tmp_path) + # get_config_path will return home_dir/.mne/mne-python.json + config_file = get_config_path(home_dir=home_dir) + + # Ensure that the .mne directory exists. + config_dir = tmp_path / ".mne" + config_dir.mkdir(exist_ok=True) + + # Write an initial (valid) config file. + initial_config = {"initial": "True"} + with open(config_file, "w") as f: + json.dump(initial_config, f) + + n_workers = 50 + iterations = 20 + + # Launch multiple workers concurrently using joblib. + Parallel(n_jobs=25)( + delayed(_worker_update_config_loop)(home_dir, worker_id, iterations) + for worker_id in range(n_workers) + ) + + # Now, read back the config file. + final_config = get_config(home_dir=home_dir) + + # For each worker and iteration, check that the expected key/value pair is present. + for worker_id in range(n_workers): + for i in range(iterations): + expected_key = f"worker_{worker_id}_{i}" + expected_value = f"value_{worker_id}_{i}" + assert final_config.get(expected_key) == expected_value, ( + f"Missing or incorrect value for key {expected_key}" + ) From 8d3fd79290fca3e3dc58f5ee1180f5008ffd8664 Mon Sep 17 00:00:00 2001 From: bruAristimunha Date: Mon, 5 May 2025 22:21:58 +0200 Subject: [PATCH 04/14] whats new file --- doc/changes/devel/13235.bugfix.rst | 1 + 1 file changed, 1 insertion(+) create mode 100644 doc/changes/devel/13235.bugfix.rst diff --git a/doc/changes/devel/13235.bugfix.rst b/doc/changes/devel/13235.bugfix.rst new file mode 100644 index 00000000000..1cadfb83aea --- /dev/null +++ b/doc/changes/devel/13235.bugfix.rst @@ -0,0 +1 @@ +Improved the :meth:`mne.utils.get_config` and :meth:`mne.utils.set_config` with parallel access with file lock configuration by :newcontrib:`Bruno Aristimunha`. \ No newline at end of file From 717f2f5e963300c6623af397f30b005aea2c75c7 Mon Sep 17 00:00:00 2001 From: bruAristimunha Date: Tue, 6 May 2025 13:33:51 +0200 Subject: [PATCH 05/14] done --- mne/utils/config.py | 49 +++++++++++++++++++++++++++------- mne/utils/tests/test_config.py | 27 ++++++++++++++++--- 2 files changed, 62 insertions(+), 14 deletions(-) diff --git a/mne/utils/config.py b/mne/utils/config.py index dac31804137..cccf14a2322 100644 --- a/mne/utils/config.py +++ b/mne/utils/config.py @@ -226,7 +226,7 @@ def set_memmap_min_size(memmap_min_size): @contextlib.contextmanager -def _path_lock(path): +def _open_lock(path, *args, **kwargs): """ Context manager that opens a file with an optional file lock. @@ -240,28 +240,39 @@ def _path_lock(path): ---------- path : str The path to the file to be opened. + *args, **kwargs : optional + Additional arguments and keyword arguments to be passed to the + `open` function. """ - filelock = _soft_import("filelock", purpose="parallel integration", strict=False) + filelock = _soft_import( + "filelock", purpose="parallel config set and get", strict=False + ) + + lock_context = contextlib.nullcontext() # default to no lock if filelock is not None: lock_path = f"{path}.lock" try: from filelock import FileLock - lock_context = FileLock(lock_path, timeout=2) + lock_context = FileLock(lock_path, timeout=5) + lock_context.acquire() except TimeoutError: lock_context = contextlib.nullcontext() - else: - lock_context = contextlib.nullcontext() - with lock_context: - yield path + try: + with lock_context: + with open(path, *args, **kwargs) as fid: + yield fid + finally: + # needed to release the lock + pass def _load_config(config_path, raise_error=False): """Safely load a config file.""" - with open(_path_lock(config_path)) as fid: + with _open_lock(config_path, "r+") as fid: try: config = json.load(fid) except ValueError: @@ -439,8 +450,26 @@ def set_config(key, value, home_dir=None, set_env=True): directory = op.dirname(config_path) if not op.isdir(directory): os.mkdir(directory) - with open(_path_lock(config_path), "w") as fid: - json.dump(config, fid, sort_keys=True, indent=0) + + # Adapting the mode depend if you are create the file + # or no. + mode = "r+" if op.isfile(config_path) else "w+" + + with _open_lock(config_path, mode) as fid: + try: + fid.seek(0) + data = json.load(fid) + except (ValueError, json.JSONDecodeError): + data = {} + + if value is None: + data.pop(key, None) + else: + data[key] = value + + fid.seek(0) + fid.truncate() + json.dump(data, fid, sort_keys=True, indent=0) def _get_extra_data_path(home_dir=None): diff --git a/mne/utils/tests/test_config.py b/mne/utils/tests/test_config.py index 9b1c49b9868..ab84645282e 100644 --- a/mne/utils/tests/test_config.py +++ b/mne/utils/tests/test_config.py @@ -277,7 +277,7 @@ def test_parallel_get_set_config(tmp_path): """ pytest.importorskip("joblib") - pytest.importorskip("FileLock") + pytest.importorskip("filelock") from joblib import Parallel, delayed # Use the temporary directory as our home directory. @@ -295,22 +295,41 @@ def test_parallel_get_set_config(tmp_path): json.dump(initial_config, f) n_workers = 50 - iterations = 20 + iterations = 10 # Launch multiple workers concurrently using joblib. - Parallel(n_jobs=25)( + Parallel(n_jobs=10)( delayed(_worker_update_config_loop)(home_dir, worker_id, iterations) for worker_id in range(n_workers) ) # Now, read back the config file. final_config = get_config(home_dir=home_dir) - + expected_keys = set() + expected_values = set() # For each worker and iteration, check that the expected key/value pair is present. for worker_id in range(n_workers): for i in range(iterations): expected_key = f"worker_{worker_id}_{i}" expected_value = f"value_{worker_id}_{i}" + assert final_config.get(expected_key) == expected_value, ( f"Missing or incorrect value for key {expected_key}" ) + expected_keys.add(expected_key) + expected_values.add(expected_value) + + # include the initial key/value pair + # that was written before the workers started + expected_keys.add("initial") + expected_values.add("True") + + assert len(set(final_config.keys()) - expected_keys) == 0 + assert len(set(final_config.values()) - expected_values) == 0 + + # Check that the final config is valid JSON. + with open(config_file) as f: + try: + json.load(f) + except json.JSONDecodeError as e: + pytest.fail(f"Config file is not valid JSON: {e}") From 519ada35ad446b5032a83109da1aac77def862d5 Mon Sep 17 00:00:00 2001 From: bruAristimunha Date: Tue, 6 May 2025 13:37:25 +0200 Subject: [PATCH 06/14] updating number for the whats new --- doc/changes/devel/{13235.bugfix.rst => 13241.bugfix.rst} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename doc/changes/devel/{13235.bugfix.rst => 13241.bugfix.rst} (100%) diff --git a/doc/changes/devel/13235.bugfix.rst b/doc/changes/devel/13241.bugfix.rst similarity index 100% rename from doc/changes/devel/13235.bugfix.rst rename to doc/changes/devel/13241.bugfix.rst From ebb93738340c2f638a70cb4f4b4c117ec748f173 Mon Sep 17 00:00:00 2001 From: bruAristimunha Date: Tue, 6 May 2025 13:56:32 +0200 Subject: [PATCH 07/14] updating the description --- doc/changes/devel/13241.bugfix.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/changes/devel/13241.bugfix.rst b/doc/changes/devel/13241.bugfix.rst index 1cadfb83aea..6834aade311 100644 --- a/doc/changes/devel/13241.bugfix.rst +++ b/doc/changes/devel/13241.bugfix.rst @@ -1 +1 @@ -Improved the :meth:`mne.utils.get_config` and :meth:`mne.utils.set_config` with parallel access with file lock configuration by :newcontrib:`Bruno Aristimunha`. \ No newline at end of file +Improved the configuration json to handle with parallel access with file lock configuration by :newcontrib:`Bruno Aristimunha`. \ No newline at end of file From e3bbf9801f52486ee76ca40736b7af9a4065a6f1 Mon Sep 17 00:00:00 2001 From: bruAristimunha Date: Tue, 6 May 2025 17:27:34 +0200 Subject: [PATCH 08/14] updating the test to erase the config file if exist --- mne/utils/tests/test_config.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/mne/utils/tests/test_config.py b/mne/utils/tests/test_config.py index ab84645282e..d67dd86c3c5 100644 --- a/mne/utils/tests/test_config.py +++ b/mne/utils/tests/test_config.py @@ -268,7 +268,7 @@ def _worker_update_config_loop(home_dir, worker_id, iterations=10): return worker_id -def test_parallel_get_set_config(tmp_path): +def test_parallel_get_set_config(tmp_path: Path): """Test that uses parallel workers to get and set config. All the workers update the same configuration file concurrently. In a @@ -285,6 +285,10 @@ def test_parallel_get_set_config(tmp_path): # get_config_path will return home_dir/.mne/mne-python.json config_file = get_config_path(home_dir=home_dir) + # if the config file already exists, remove it + if os.path.exists(config_file): + os.remove(config_file) + # Ensure that the .mne directory exists. config_dir = tmp_path / ".mne" config_dir.mkdir(exist_ok=True) From 180838763ced5bf29622ddc661bf52ec5e82260a Mon Sep 17 00:00:00 2001 From: bruAristimunha Date: Tue, 6 May 2025 18:30:54 +0200 Subject: [PATCH 09/14] done v3 --- mne/utils/tests/test_config.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/mne/utils/tests/test_config.py b/mne/utils/tests/test_config.py index d67dd86c3c5..c87a8895c34 100644 --- a/mne/utils/tests/test_config.py +++ b/mne/utils/tests/test_config.py @@ -325,11 +325,9 @@ def test_parallel_get_set_config(tmp_path: Path): # include the initial key/value pair # that was written before the workers started - expected_keys.add("initial") - expected_values.add("True") - assert len(set(final_config.keys()) - expected_keys) == 0 - assert len(set(final_config.values()) - expected_values) == 0 + assert len(expected_keys - set(final_config.keys())) == 0 + assert len(expected_values - set(final_config.values())) == 0 # Check that the final config is valid JSON. with open(config_file) as f: From 5117342702dd9235aeda7a01f73c9dca992690e6 Mon Sep 17 00:00:00 2001 From: Bru Date: Tue, 13 May 2025 16:20:12 +0200 Subject: [PATCH 10/14] Apply suggestions from code review Co-authored-by: Eric Larson --- mne/utils/config.py | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/mne/utils/config.py b/mne/utils/config.py index cccf14a2322..b4c6382e48a 100644 --- a/mne/utils/config.py +++ b/mne/utils/config.py @@ -259,15 +259,14 @@ def _open_lock(path, *args, **kwargs): lock_context = FileLock(lock_path, timeout=5) lock_context.acquire() except TimeoutError: + warn( + "Could not acquire lock file after 5 seconds, consider deleting it " + f"if you know the corresponding file is usable:\n{lock_path}" + ) lock_context = contextlib.nullcontext() - try: - with lock_context: - with open(path, *args, **kwargs) as fid: - yield fid - finally: - # needed to release the lock - pass + with lock_context, open(path, *args, **kwargs) as fid: + yield fid def _load_config(config_path, raise_error=False): From 73d2980e2fd2a367d9a633a988b2a1242cf7af08 Mon Sep 17 00:00:00 2001 From: bruAristimunha Date: Tue, 13 May 2025 16:32:43 +0200 Subject: [PATCH 11/14] updating the config --- mne/utils/config.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/mne/utils/config.py b/mne/utils/config.py index b4c6382e48a..2e215d104fd 100644 --- a/mne/utils/config.py +++ b/mne/utils/config.py @@ -456,9 +456,10 @@ def set_config(key, value, home_dir=None, set_env=True): with _open_lock(config_path, mode) as fid: try: - fid.seek(0) data = json.load(fid) except (ValueError, json.JSONDecodeError): + msg = f"Could not read the {config_path} json file, creating a new one." + warn(msg) data = {} if value is None: From 7c3b01c17c26df69bbde80fa02794db91997dc57 Mon Sep 17 00:00:00 2001 From: bruAristimunha Date: Tue, 13 May 2025 16:37:42 +0200 Subject: [PATCH 12/14] exporing the "_open_lock" into the init --- mne/forward/_lead_dots.py | 6 +++--- mne/utils/__init__.pyi | 2 ++ 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/mne/forward/_lead_dots.py b/mne/forward/_lead_dots.py index 5db396224b5..10183fc6280 100644 --- a/mne/forward/_lead_dots.py +++ b/mne/forward/_lead_dots.py @@ -12,7 +12,7 @@ from numpy.polynomial import legendre from ..parallel import parallel_func -from ..utils import _get_extra_data_path, fill_doc, logger, verbose +from ..utils import _get_extra_data_path, _open_lock, fill_doc, logger, verbose ############################################################################## # FAST LEGENDRE (DERIVATIVE) POLYNOMIALS USING LOOKUP TABLE @@ -80,11 +80,11 @@ def _get_legen_table( x_interp = np.linspace(-1, 1, n_interp + 1) lut = leg_fun(x_interp, n_coeff).astype(np.float32) if not force_calc: - with open(fname, "wb") as fid: + with _open_lock(fname, "wb") as fid: fid.write(lut.tobytes()) else: logger.info(f"Reading Legendre{extra_str} table...") - with open(fname, "rb", buffering=0) as fid: + with _open_lock(fname, "rb", buffering=0) as fid: lut = np.fromfile(fid, np.float32) lut.shape = lut_shape diff --git a/mne/utils/__init__.pyi b/mne/utils/__init__.pyi index 46d272e972d..6e207423846 100644 --- a/mne/utils/__init__.pyi +++ b/mne/utils/__init__.pyi @@ -88,6 +88,7 @@ __all__ = [ "_julian_to_date", "_mask_to_onsets_offsets", "_on_missing", + "_open_lock", "_parse_verbose", "_path_like", "_pl", @@ -280,6 +281,7 @@ from .config import ( _get_numpy_libs, _get_root_dir, _get_stim_channel, + _open_lock, get_config, get_config_path, get_subjects_dir, From 5ff6e2f2d17c3c0237d40427da018075863254e5 Mon Sep 17 00:00:00 2001 From: bruAristimunha Date: Tue, 13 May 2025 18:06:52 +0200 Subject: [PATCH 13/14] updating the config --- mne/utils/config.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/mne/utils/config.py b/mne/utils/config.py index 2e215d104fd..123e63dc7dd 100644 --- a/mne/utils/config.py +++ b/mne/utils/config.py @@ -458,8 +458,10 @@ def set_config(key, value, home_dir=None, set_env=True): try: data = json.load(fid) except (ValueError, json.JSONDecodeError): - msg = f"Could not read the {config_path} json file, creating a new one." - warn(msg) + logger.info( + f"Could not read the {config_path} json file during the writing." + " Assuming it is empty." + ) data = {} if value is None: From d036a6cff9bf09760606ea05ec912b47f4eb09bf Mon Sep 17 00:00:00 2001 From: Eric Larson Date: Thu, 15 May 2025 11:08:55 -0400 Subject: [PATCH 14/14] Update mne/utils/config.py --- mne/utils/config.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/mne/utils/config.py b/mne/utils/config.py index 123e63dc7dd..beaaab80a58 100644 --- a/mne/utils/config.py +++ b/mne/utils/config.py @@ -457,10 +457,10 @@ def set_config(key, value, home_dir=None, set_env=True): with _open_lock(config_path, mode) as fid: try: data = json.load(fid) - except (ValueError, json.JSONDecodeError): + except (ValueError, json.JSONDecodeError) as exc: logger.info( f"Could not read the {config_path} json file during the writing." - " Assuming it is empty." + f" Assuming it is empty. Got: {exc}" ) data = {}