From a69d22b17eed21e0e1eeb5762c8f7cc330d90785 Mon Sep 17 00:00:00 2001
From: Aniket Singh Yadav <singhyadavaniket43@gmail.com>
Date: Sat, 21 Feb 2026 04:35:05 +0000
Subject: [PATCH 1/8] =?UTF-8?q?clarify=20duplicate=20private=E2=80=91submo?=
 =?UTF-8?q?dule=20checks=20in=20credit=5Ftools.py?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 doc/sphinxext/credit_tools.py         | 17 ++++++++--
 mne/{_edf/open.py => io/edf/_open.py} | 47 ++++++++++++++-------------
 mne/io/edf/edf.py                     |  2 +-
 3 files changed, 40 insertions(+), 26 deletions(-)
 rename mne/{_edf/open.py => io/edf/_open.py} (76%)

diff --git a/doc/sphinxext/credit_tools.py b/doc/sphinxext/credit_tools.py
index 8f8b8c00a22..932129412ba 100644
--- a/doc/sphinxext/credit_tools.py
+++ b/doc/sphinxext/credit_tools.py
@@ -382,8 +382,12 @@ def generate_credit_rst(app=None, *, verbose=False):
             other_files.add(fname)
             mod = "other"
         for e, pm in counts.items():
-            if mod == "mne._fiff":
-                raise RuntimeError
+            # Assert no private (_-prefixed) submodules appear without a redirect
+            if mod.startswith("mne.") and mod.split(".")[-1].startswith("_"):
+                raise RuntimeError(
+                    f"Private submodule {mod!r} found in credit page for {fname!r}. "
+                    "Add an override in credit_tools.py to remap it to a public module."
+                )
             # sanity check a bit
             if mod != "null" and (".png" in fname or "/manual/" in fname):
                 raise RuntimeError(f"Unexpected {mod} {fname}")
@@ -391,6 +395,15 @@ def generate_credit_rst(app=None, *, verbose=False):
             mod_stats["mne"][e] += pm
             total_lines += pm
     mod_stats.pop("null")  # stuff we shouldn't give credit for
+    # Assert no private (_-prefixed) submodule names remain after null-removal
+    private_mods = [
+        m for m in mod_stats if m.startswith("mne.") and m.split(".")[-1].startswith("_")
+    ]
+    if private_mods:
+        raise RuntimeError(
+            f"Private submodule(s) {private_mods} found in credit page. "
+            "Update credit_tools.py to remap them to a public module."
+        )
     mod_stats = dict(
         (k, mod_stats[k])
         for k in sorted(
diff --git a/mne/_edf/open.py b/mne/io/edf/_open.py
similarity index 76%
rename from mne/_edf/open.py
rename to mne/io/edf/_open.py
index 2fd97833b29..47b2e89ba7e 100644
--- a/mne/_edf/open.py
+++ b/mne/io/edf/_open.py
@@ -1,23 +1,24 @@
-# Authors: The MNE-Python contributors.
-# License: BSD-3-Clause
-# Copyright the MNE-Python contributors.
-
-# Maybe we can move this one to utils or something like that.
-from pathlib import Path
-
-from mne._fiff.open import _NoCloseRead
-
-from ..utils import _file_like, _validate_type, logger
-
-
-def _gdf_edf_get_fid(fname, **kwargs):
-    """Open a EDF/BDF/GDF file with no additional parsing."""
-    if _file_like(fname):
-        logger.debug("Using file-like I/O")
-        fid = _NoCloseRead(fname)
-        fid.seek(0)
-    else:
-        _validate_type(fname, [Path, str], "fname", extra="or file-like")
-        logger.debug("Using normal I/O")
-        fid = open(fname, "rb", **kwargs)  # Open in binary mode
-    return fid
+"""File opening utilities for EDF/BDF/GDF formats."""
+
+# Authors: The MNE-Python contributors.
+# License: BSD-3-Clause
+# Copyright the MNE-Python contributors.
+
+from pathlib import Path
+
+from ..._fiff.open import _NoCloseRead
+
+from ...utils import _file_like, _validate_type, logger
+
+
+def _gdf_edf_get_fid(fname, **kwargs):
+    """Open a EDF/BDF/GDF file with no additional parsing."""
+    if _file_like(fname):
+        logger.debug("Using file-like I/O")
+        fid = _NoCloseRead(fname)
+        fid.seek(0)
+    else:
+        _validate_type(fname, [Path, str], "fname", extra="or file-like")
+        logger.debug("Using normal I/O")
+        fid = open(fname, "rb", **kwargs)  # Open in binary mode
+    return fid
diff --git a/mne/io/edf/edf.py b/mne/io/edf/edf.py
index b1ebe3ce622..6a48a7aebc2 100644
--- a/mne/io/edf/edf.py
+++ b/mne/io/edf/edf.py
@@ -13,7 +13,7 @@
 import numpy as np
 from scipy.interpolate import interp1d
 
-from ..._edf.open import _gdf_edf_get_fid
+from ._open import _gdf_edf_get_fid
 from ..._fiff.constants import FIFF
 from ..._fiff.meas_info import _empty_info, _unique_channel_names
 from ..._fiff.utils import _blk_read_lims, _mult_cal_one

From 5e7880d48457b78222b63d56fcf89310fde2a4ce Mon Sep 17 00:00:00 2001
From: Aniket Singh Yadav <singhyadavaniket43@gmail.com>
Date: Sat, 21 Feb 2026 04:41:34 +0000
Subject: [PATCH 2/8] =?UTF-8?q?clarify=20duplicate=20private=E2=80=91submo?=
 =?UTF-8?q?dule=20checks=20in=20credit=5Ftools.py?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 mne/io/edf/_open.py | 2 --
 1 file changed, 2 deletions(-)

diff --git a/mne/io/edf/_open.py b/mne/io/edf/_open.py
index 47b2e89ba7e..80aabd30bd8 100644
--- a/mne/io/edf/_open.py
+++ b/mne/io/edf/_open.py
@@ -1,5 +1,3 @@
-"""File opening utilities for EDF/BDF/GDF formats."""
-
 # Authors: The MNE-Python contributors.
 # License: BSD-3-Clause
 # Copyright the MNE-Python contributors.

From bd0b5829f4ba1cfd9f4a5af4ee0fd4b71ae50f86 Mon Sep 17 00:00:00 2001
From: "pre-commit-ci[bot]"
 <66853113+pre-commit-ci[bot]@users.noreply.github.com>
Date: Sat, 21 Feb 2026 04:54:49 +0000
Subject: [PATCH 3/8] [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci
---
 doc/sphinxext/credit_tools.py | 4 +++-
 mne/io/edf/_open.py           | 1 -
 mne/io/edf/edf.py             | 2 +-
 3 files changed, 4 insertions(+), 3 deletions(-)

diff --git a/doc/sphinxext/credit_tools.py b/doc/sphinxext/credit_tools.py
index 932129412ba..8ec54b33d87 100644
--- a/doc/sphinxext/credit_tools.py
+++ b/doc/sphinxext/credit_tools.py
@@ -397,7 +397,9 @@ def generate_credit_rst(app=None, *, verbose=False):
     mod_stats.pop("null")  # stuff we shouldn't give credit for
     # Assert no private (_-prefixed) submodule names remain after null-removal
     private_mods = [
-        m for m in mod_stats if m.startswith("mne.") and m.split(".")[-1].startswith("_")
+        m
+        for m in mod_stats
+        if m.startswith("mne.") and m.split(".")[-1].startswith("_")
     ]
     if private_mods:
         raise RuntimeError(
diff --git a/mne/io/edf/_open.py b/mne/io/edf/_open.py
index 80aabd30bd8..d8456bac703 100644
--- a/mne/io/edf/_open.py
+++ b/mne/io/edf/_open.py
@@ -5,7 +5,6 @@
 from pathlib import Path
 
 from ..._fiff.open import _NoCloseRead
-
 from ...utils import _file_like, _validate_type, logger
 
 
diff --git a/mne/io/edf/edf.py b/mne/io/edf/edf.py
index 6a48a7aebc2..67caaa7616b 100644
--- a/mne/io/edf/edf.py
+++ b/mne/io/edf/edf.py
@@ -13,7 +13,6 @@
 import numpy as np
 from scipy.interpolate import interp1d
 
-from ._open import _gdf_edf_get_fid
 from ..._fiff.constants import FIFF
 from ..._fiff.meas_info import _empty_info, _unique_channel_names
 from ..._fiff.utils import _blk_read_lims, _mult_cal_one
@@ -30,6 +29,7 @@
     warn,
 )
 from ..base import BaseRaw, _get_scaling
+from ._open import _gdf_edf_get_fid
 
 
 class FileType(Enum):

From d6a9abae9265d7a664dbe512f445049adabeb685 Mon Sep 17 00:00:00 2001
From: Aniket Singh Yadav <singhyadavaniket43@gmail.com>
Date: Tue, 24 Feb 2026 06:43:30 +0000
Subject: [PATCH 4/8] Fix CRLF line endings

---
 mne/io/edf/_open.py | 42 +++++++++++++++++++++---------------------
 1 file changed, 21 insertions(+), 21 deletions(-)

diff --git a/mne/io/edf/_open.py b/mne/io/edf/_open.py
index d8456bac703..38f8be4113d 100644
--- a/mne/io/edf/_open.py
+++ b/mne/io/edf/_open.py
@@ -1,21 +1,21 @@
-# Authors: The MNE-Python contributors.
-# License: BSD-3-Clause
-# Copyright the MNE-Python contributors.
-
-from pathlib import Path
-
-from ..._fiff.open import _NoCloseRead
-from ...utils import _file_like, _validate_type, logger
-
-
-def _gdf_edf_get_fid(fname, **kwargs):
-    """Open a EDF/BDF/GDF file with no additional parsing."""
-    if _file_like(fname):
-        logger.debug("Using file-like I/O")
-        fid = _NoCloseRead(fname)
-        fid.seek(0)
-    else:
-        _validate_type(fname, [Path, str], "fname", extra="or file-like")
-        logger.debug("Using normal I/O")
-        fid = open(fname, "rb", **kwargs)  # Open in binary mode
-    return fid
+# Authors: The MNE-Python contributors.
+# License: BSD-3-Clause
+# Copyright the MNE-Python contributors.
+
+from pathlib import Path
+
+from ..._fiff.open import _NoCloseRead
+from ...utils import _file_like, _validate_type, logger
+
+
+def _gdf_edf_get_fid(fname, **kwargs):
+    """Open a EDF/BDF/GDF file with no additional parsing."""
+    if _file_like(fname):
+        logger.debug("Using file-like I/O")
+        fid = _NoCloseRead(fname)
+        fid.seek(0)
+    else:
+        _validate_type(fname, [Path, str], "fname", extra="or file-like")
+        logger.debug("Using normal I/O")
+        fid = open(fname, "rb", **kwargs)  # Open in binary mode
+    return fid

From 4335394c4434c2b6ba2086940268b44a0d43b972 Mon Sep 17 00:00:00 2001
From: Aniket Singh Yadav <singhyadavaniket43@gmail.com>
Date: Tue, 24 Feb 2026 06:54:11 +0000
Subject: [PATCH 5/8] Add changelog entry

---
 doc/changes/dev/13673.other.rst | 1 +
 1 file changed, 1 insertion(+)
 create mode 100644 doc/changes/dev/13673.other.rst

diff --git a/doc/changes/dev/13673.other.rst b/doc/changes/dev/13673.other.rst
new file mode 100644
index 00000000000..e93dfbcbc62
--- /dev/null
+++ b/doc/changes/dev/13673.other.rst
@@ -0,0 +1 @@
+Reorganized EDF submodule structure by moving private functionality into the public ``mne.io.edf`` namespace to improve module consistency and avoid exposing private modules in the Credits page, by `Aniket Singh Yadav`_.
\ No newline at end of file

From 3aa66189377d9098c33c0976489136a8709c07bf Mon Sep 17 00:00:00 2001
From: Aniket Singh Yadav <singhyadavaniket43@gmail.com>
Date: Fri, 13 Mar 2026 13:22:27 +0000
Subject: [PATCH 6/8] Fix-ci doc

---
 doc/sphinxext/credit_tools.py | 1 +
 1 file changed, 1 insertion(+)

diff --git a/doc/sphinxext/credit_tools.py b/doc/sphinxext/credit_tools.py
index 8ec54b33d87..92cd2147798 100644
--- a/doc/sphinxext/credit_tools.py
+++ b/doc/sphinxext/credit_tools.py
@@ -335,6 +335,7 @@ def generate_credit_rst(app=None, *, verbose=False):
     globs["bin/*"] = "mne.commands"
     globs["mne/morph_map.py"] = "mne.surface"
     globs["mne/baseline.py"] = "mne.epochs"
+    globs["mne/_edf/open.py"] = "mne.io.edf"
     for key in """
         parallel.py rank.py misc.py data/*.* defaults.py fixes.py icons/*.* icons.*
     """.strip().split():

From d68db1c44a27aba3addc5c259f1345401729f982 Mon Sep 17 00:00:00 2001
From: Aniket Singh Yadav <singhyadavaniket43@gmail.com>
Date: Fri, 3 Apr 2026 09:02:57 +0000
Subject: [PATCH 7/8] update location

---
 doc/changes/dev/13673.other.rst | 2 +-
 doc/sphinxext/credit_tools.py   | 2 +-
 2 files changed, 2 insertions(+), 2 deletions(-)

diff --git a/doc/changes/dev/13673.other.rst b/doc/changes/dev/13673.other.rst
index e93dfbcbc62..1dd6f7f4d52 100644
--- a/doc/changes/dev/13673.other.rst
+++ b/doc/changes/dev/13673.other.rst
@@ -1 +1 @@
-Reorganized EDF submodule structure by moving private functionality into the public ``mne.io.edf`` namespace to improve module consistency and avoid exposing private modules in the Credits page, by `Aniket Singh Yadav`_.
\ No newline at end of file
+Reorganized EDF submodule structure by moving private functionality into the public ``mne.io`` namespace to improve module consistency and avoid exposing private modules in the Credits page, by `Aniket Singh Yadav`_.
\ No newline at end of file
diff --git a/doc/sphinxext/credit_tools.py b/doc/sphinxext/credit_tools.py
index 92cd2147798..92e80dd6a16 100644
--- a/doc/sphinxext/credit_tools.py
+++ b/doc/sphinxext/credit_tools.py
@@ -335,7 +335,7 @@ def generate_credit_rst(app=None, *, verbose=False):
     globs["bin/*"] = "mne.commands"
     globs["mne/morph_map.py"] = "mne.surface"
     globs["mne/baseline.py"] = "mne.epochs"
-    globs["mne/_edf/open.py"] = "mne.io.edf"
+    globs["mne/io/edf/_open.py"] = "mne.io"
     for key in """
         parallel.py rank.py misc.py data/*.* defaults.py fixes.py icons/*.* icons.*
     """.strip().split():

From c726085eb1714abde0de003f33aa0a09f1f4a3bb Mon Sep 17 00:00:00 2001
From: Aniket Singh Yadav <singhyadavaniket43@gmail.com>
Date: Sat, 4 Apr 2026 19:04:07 +0000
Subject: [PATCH 8/8] fix ci-doc

---
 doc/sphinxext/credit_tools.py | 1 +
 1 file changed, 1 insertion(+)

diff --git a/doc/sphinxext/credit_tools.py b/doc/sphinxext/credit_tools.py
index 92e80dd6a16..4cbbaa76b44 100644
--- a/doc/sphinxext/credit_tools.py
+++ b/doc/sphinxext/credit_tools.py
@@ -336,6 +336,7 @@ def generate_credit_rst(app=None, *, verbose=False):
     globs["mne/morph_map.py"] = "mne.surface"
     globs["mne/baseline.py"] = "mne.epochs"
     globs["mne/io/edf/_open.py"] = "mne.io"
+    globs["mne/_edf/open.py"] = "mne.io"
     for key in """
         parallel.py rank.py misc.py data/*.* defaults.py fixes.py icons/*.* icons.*
     """.strip().split():
