diff --git a/bluecellulab/cell/core.py b/bluecellulab/cell/core.py index 9a42d910..51f59a36 100644 --- a/bluecellulab/cell/core.py +++ b/bluecellulab/cell/core.py @@ -97,12 +97,6 @@ def __init__(self, emodel_properties: Template specific emodel properties. """ super().__init__() - self.template_params = TemplateParams( - template_filepath=template_path, - morph_filepath=morphology_path, - template_format=template_format, - emodel_properties=emodel_properties, - ) if cell_id is None: cell_id = CellId("", Cell.last_id) Cell.last_id += 1 @@ -111,6 +105,12 @@ def __init__(self, # Load the template neuron_template = NeuronTemplate(template_path, morphology_path, template_format, emodel_properties) + self.template_params = TemplateParams( + template_filepath=template_path, + morph_filepath=neuron_template.morph_filepath, + template_format=template_format, + emodel_properties=emodel_properties, + ) self.template_id = neuron_template.template_name # useful to map NEURON and python objects self.cell = neuron_template.get_cell(self.cell_id.id) if template_format == 'v6': diff --git a/bluecellulab/cell/morphio_wrapper.py b/bluecellulab/cell/morphio_wrapper.py index 20b53f3b..e9ce3f2f 100644 --- a/bluecellulab/cell/morphio_wrapper.py +++ b/bluecellulab/cell/morphio_wrapper.py @@ -357,7 +357,6 @@ def __init__(self, input_file, options=0): if not is_h5_container_path(input_file): input_file = resolve_case_insensitive_path(input_file) self._collection_dir, self._morph_name, self._morph_ext = split_morphology_path(input_file) - self._morph_ext = self._morph_ext.lower() self._options = options self._build_morph() # This logic is similar to what's in BaseCell, but at this point we are still diff --git a/bluecellulab/cell/template.py b/bluecellulab/cell/template.py index 90e45a2c..a08cf093 100644 --- a/bluecellulab/cell/template.py +++ b/bluecellulab/cell/template.py @@ -25,7 +25,7 @@ import neuron -from bluecellulab.cell.morphio_wrapper import split_morphology_path +from bluecellulab.cell.morphio_wrapper import is_h5_container_path, resolve_case_insensitive_path, split_morphology_path from bluecellulab.circuit import EmodelProperties from bluecellulab.exceptions import BluecellulabError from bluecellulab.type_aliases import HocObjectType @@ -110,6 +110,9 @@ def __init__( if not os.path.exists(template_filepath): raise FileNotFoundError(f"Couldn't find template file: {template_filepath}") + if not is_h5_container_path(morph_filepath): + morph_filepath = resolve_case_insensitive_path(morph_filepath) + # Check morphology path - handle H5 container paths if not self._is_valid_morphology_path(morph_filepath): raise FileNotFoundError(f"Couldn't find morphology file: {morph_filepath}") diff --git a/tests/test_cell/test_morphology_wrapper.py b/tests/test_cell/test_morphology_wrapper.py index 9bf4aca0..dbe1e7d6 100644 --- a/tests/test_cell/test_morphology_wrapper.py +++ b/tests/test_cell/test_morphology_wrapper.py @@ -9,6 +9,7 @@ SectionName, split_morphology_path, ) +from bluecellulab.cell.template import NeuronTemplate class TestMorphologyWrapper: @@ -59,42 +60,52 @@ def test_split_morphology_path_container_with_cell_name(self, tmp_path): assert morph_name == "cell_name" assert morph_ext == "" - def test_case_insensitive_morphology_path(self, tmp_path): + def test_case_insensitive_morphology_path_linux_like(self, tmp_path): p = tmp_path / "cell.ASC" p.write_text("dummy") - with patch.object(MorphIOWrapper, "_build_morph"): - with patch.object(MorphIOWrapper, "_get_section_names", return_value=[]): - with patch.object(MorphIOWrapper, "_build_sec_typeid_distrib"): - wrapper = MorphIOWrapper(str(tmp_path / "cell.asc")) + wrong_case = tmp_path / "cell.asc" + original_exists = Path.exists + + def linux_like_exists(path): + if path == wrong_case: + return False + return original_exists(path) + + with ( + patch.object(Path, "exists", linux_like_exists), + patch.object(MorphIOWrapper, "_build_morph"), + patch.object(MorphIOWrapper, "_get_section_names", return_value=[]), + patch.object(MorphIOWrapper, "_build_sec_typeid_distrib"), + ): + wrapper = MorphIOWrapper(str(wrong_case)) assert wrapper._morph_name == "cell" - assert wrapper._morph_ext == ".asc" + assert wrapper._morph_ext == ".ASC" - def test_case_insensitive_morphology_path_reverse(self, tmp_path): - """Should also work if file is lowercase and input is uppercase.""" - p = tmp_path / "cell.asc" - p.write_text("dummy") + def test_neuron_template_resolves_case_insensitive_morphology_path(self, tmp_path): + """NeuronTemplate should validate wrong-case morphology filenames.""" + morph_file = tmp_path / "cell.ASC" + morph_file.write_text("dummy") - with patch.object(MorphIOWrapper, "_build_morph"): - with patch.object(MorphIOWrapper, "_get_section_names", return_value=[]): - with patch.object(MorphIOWrapper, "_build_sec_typeid_distrib"): - wrapper = MorphIOWrapper(str(tmp_path / "cell.ASC")) + template_file = tmp_path / "template.hoc" + template_file.write_text("begintemplate T\nendtemplate T\n") - assert wrapper._morph_name == "cell" - assert wrapper._morph_ext == ".asc" + wrong_case = tmp_path / "cell.asc" + original_exists = Path.exists - def test_extension_normalization(self, tmp_path): - """Extension should always be normalized to lowercase.""" - p = tmp_path / "cell.SWC" - p.write_text("dummy") + def linux_like_exists(path): + if path == wrong_case: + return False + return original_exists(path) - with patch.object(MorphIOWrapper, "_build_morph"): - with patch.object(MorphIOWrapper, "_get_section_names", return_value=[]): - with patch.object(MorphIOWrapper, "_build_sec_typeid_distrib"): - wrapper = MorphIOWrapper(str(p)) + with ( + patch.object(Path, "exists", linux_like_exists), + patch.object(NeuronTemplate, "load", return_value="T"), + ): + template = NeuronTemplate(str(template_file), str(wrong_case), "v5", None) - assert wrapper._morph_ext == ".swc" + assert template.morph_filepath == str(morph_file) def test_morphology_wrapper_init_success(self): """Test successful MorphologyWrapper initialization with real H5 file.""" diff --git a/tox.ini b/tox.ini index 27621d68..d7b2954b 100644 --- a/tox.ini +++ b/tox.ini @@ -41,7 +41,7 @@ deps = pandas-stubs>=2.0.0 types-setuptools>=67.8.0.0 ruff>=0.0.270 - docformatter>=1.7.2 + docformatter>=1.7.2,<1.7.8 # docformatter 1.7.8 has tokenisation regressions; pin until upstream fixes commands = ruff check . --select F541,F401 --per-file-ignores="__init__.py:F401" pycodestyle {[base]name} --ignore=E501,W504,W503