From c69993fb69e38cda83a0d2cf254feda87ae888ef Mon Sep 17 00:00:00 2001 From: Robert Vickerstaff Date: Thu, 26 Oct 2023 17:39:56 +0000 Subject: [PATCH 1/6] initial attempt at a validate-sbml option for pynml.py --- pyneuroml/pynml.py | 11 +++++ pyneuroml/sbml/__init__.py | 64 ++++++++++++++++++++++++++++++ tests/sbml/test_data/test_doc.sbml | 42 ++++++++++++++++++++ tests/sbml/test_sbml.py | 6 +++ 4 files changed, 123 insertions(+) create mode 100644 pyneuroml/sbml/__init__.py create mode 100644 tests/sbml/test_data/test_doc.sbml create mode 100755 tests/sbml/test_sbml.py diff --git a/pyneuroml/pynml.py b/pyneuroml/pynml.py index df1f45bb1..da1c7f5ea 100644 --- a/pyneuroml/pynml.py +++ b/pyneuroml/pynml.py @@ -345,6 +345,11 @@ def parse_arguments(): action="store_true", help=("(Via jNeuroML) Validate NeuroML file(s) against the\n" "v1.8.1 Schema"), ) + mut_exc_opts.add_argument( + "-validate-sbml", + action="store_true", + help=("Validate SBML file(s)"), + ) return parser.parse_args() @@ -2111,6 +2116,12 @@ def evaluate_arguments(args): post_args = "" exit_on_fail = True + # Deal with the SBML validation option which doesn't call run_jneuroml + if args.validate_sbml: + from pyneuroml.sbml import validate_sbml_files + validate_sbml_files(" ".join(args.input_files)) + return True + # These do not use the shared option where files are supplied # They require the file name to be specified after # TODO: handle these better diff --git a/pyneuroml/sbml/__init__.py b/pyneuroml/sbml/__init__.py new file mode 100644 index 000000000..152dafd3f --- /dev/null +++ b/pyneuroml/sbml/__init__.py @@ -0,0 +1,64 @@ +""" +use libsbml.SBMLDocument.checkConsistency to check validaity of an SBML document +based on https://github.com/combine-org/combine-notebooks/blob/main/src/combine_notebooks/validation/validation_sbml.py +""" + +import os +import libsbml +from libsbml import SBMLDocument, SBMLReader + +def validate_sbml_files(input_files : str,units_consistency: bool = False): + ''' + validate each input file using libsbml.SBMLDocument.checkConsistency + ''' + file_list = input_files.split() + + for file_name in file_list: + if not os.path.isfile(file_name): + raise OSError( + ("Could not find SBML file %s" % file_name) + ) + + try: + reader = SBMLReader() + doc = reader.readSBML(file_name) + except: + raise OSError( + ("SBMLReader failed to load the file %s" % file_name) + ) + + # set the unit checking, similar for the other settings + doc.setConsistencyChecks(libsbml.LIBSBML_CAT_UNITS_CONSISTENCY, units_consistency) + doc.checkConsistency() + # get errors/warnings + n_errors: int = doc.getNumErrors() + errors: List[libsbml.SBMLError] = list() + warnings: List[libsbml.SBMLError] = list() + for k in range(n_errors): + error: libsbml.SBMLError = doc.getError(k) + severity = error.getSeverity() + if (severity == libsbml.LIBSBML_SEV_ERROR) or ( + severity == libsbml.LIBSBML_SEV_FATAL + ): + errors.append(error) + else: + warnings.append(error) + # print results + print("-" * 80) + print(f"{'validation error(s)':<25}: {len(errors)}") + print(f"{'validation warning(s)':<25}: {len(warnings)}") + if len(errors) > 0: + print("--- errors ---") + for kerr in enumerate(errors): + print(f"E{kerr}: {error.getCategoryAsString()} L{error.getLine()}") + print( + f"[{error.getSeverityAsString()}] {error.getShortMessage()} | {error.getMessage()}" + ) + if len(warnings) > 0: + print("--- warnings ---") + for kwarn in enumerate(warnings): + print(f"E{kwarn}: {error.getCategoryAsString()} L{error.getLine()}") + print( + f"[{error.getSeverityAsString()}] {error.getShortMessage()} | {error.getMessage()}" + ) + print("-" * 80) \ No newline at end of file diff --git a/tests/sbml/test_data/test_doc.sbml b/tests/sbml/test_data/test_doc.sbml new file mode 100644 index 000000000..cd05db813 --- /dev/null +++ b/tests/sbml/test_data/test_doc.sbml @@ -0,0 +1,42 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + k + S1 + c1 + + + + + + + \ No newline at end of file diff --git a/tests/sbml/test_sbml.py b/tests/sbml/test_sbml.py new file mode 100755 index 000000000..51609f624 --- /dev/null +++ b/tests/sbml/test_sbml.py @@ -0,0 +1,6 @@ +#!/usr/bin/env python3 + +from pyneuroml import sbml + +fname = "test_data/test_doc.sbml" +doc = sbml.validate_sbml_files(fname) From 0b471e1d1769abc7fd38cae5166cb754906dec32 Mon Sep 17 00:00:00 2001 From: Robert Vickerstaff Date: Thu, 26 Oct 2023 20:14:25 +0000 Subject: [PATCH 2/6] minor tweaks --- pyneuroml/sbml/__init__.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pyneuroml/sbml/__init__.py b/pyneuroml/sbml/__init__.py index 152dafd3f..45ef2bbd7 100644 --- a/pyneuroml/sbml/__init__.py +++ b/pyneuroml/sbml/__init__.py @@ -10,10 +10,10 @@ def validate_sbml_files(input_files : str,units_consistency: bool = False): ''' validate each input file using libsbml.SBMLDocument.checkConsistency + input_files is a space separated list of one or more filepaths ''' - file_list = input_files.split() - for file_name in file_list: + for file_name in input_files.split(): if not os.path.isfile(file_name): raise OSError( ("Could not find SBML file %s" % file_name) From ec6f9c425f7199341299836bb5ced3ec631eda56 Mon Sep 17 00:00:00 2001 From: Robert Vickerstaff Date: Fri, 27 Oct 2023 16:50:53 +0000 Subject: [PATCH 3/6] added libsbml as an optional extra requirement --- setup.cfg | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/setup.cfg b/setup.cfg index 6c9339eff..07b448104 100644 --- a/setup.cfg +++ b/setup.cfg @@ -101,6 +101,9 @@ plotly = nsg = pynsgr +sbml = + libsbml + all = pyNeuroML[neuron] pyNeuroML[brian] @@ -113,6 +116,7 @@ all = pyNeuroML[vispy] pyNeuroML[plotly] pyNeuroML[nsg] + pyNeuroML[sbml] dev = pyNeuroML[all] From 50d45e37e67f2b43f046e84fce64bfbeee121d90 Mon Sep 17 00:00:00 2001 From: Robert Vickerstaff Date: Fri, 27 Oct 2023 16:58:43 +0000 Subject: [PATCH 4/6] fix package name --- setup.cfg | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.cfg b/setup.cfg index 07b448104..4e672195b 100644 --- a/setup.cfg +++ b/setup.cfg @@ -102,7 +102,7 @@ nsg = pynsgr sbml = - libsbml + python-libsbml all = pyNeuroML[neuron] From 375e4eee053e9bb365557afc13b05f8647a61bf0 Mon Sep 17 00:00:00 2001 From: Robert Vickerstaff Date: Mon, 30 Oct 2023 10:26:20 +0000 Subject: [PATCH 5/6] pyneuroml/sbml/__init__.py now passing flake8 and black --- pyneuroml/sbml/__init__.py | 26 +++++++++++++------------- tests/sbml/test_sbml.py | 2 +- 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/pyneuroml/sbml/__init__.py b/pyneuroml/sbml/__init__.py index 45ef2bbd7..615bbfa03 100644 --- a/pyneuroml/sbml/__init__.py +++ b/pyneuroml/sbml/__init__.py @@ -5,30 +5,30 @@ import os import libsbml -from libsbml import SBMLDocument, SBMLReader +from libsbml import SBMLReader +from typing import List -def validate_sbml_files(input_files : str,units_consistency: bool = False): - ''' + +def validate_sbml_files(input_files: str, units_consistency: bool = False): + """ validate each input file using libsbml.SBMLDocument.checkConsistency input_files is a space separated list of one or more filepaths - ''' + """ for file_name in input_files.split(): if not os.path.isfile(file_name): - raise OSError( - ("Could not find SBML file %s" % file_name) - ) + raise OSError(("Could not find SBML file %s" % file_name)) try: reader = SBMLReader() doc = reader.readSBML(file_name) - except: - raise OSError( - ("SBMLReader failed to load the file %s" % file_name) - ) + except Exception: + raise OSError(("SBMLReader failed to load the file %s" % file_name)) # set the unit checking, similar for the other settings - doc.setConsistencyChecks(libsbml.LIBSBML_CAT_UNITS_CONSISTENCY, units_consistency) + doc.setConsistencyChecks( + libsbml.LIBSBML_CAT_UNITS_CONSISTENCY, units_consistency + ) doc.checkConsistency() # get errors/warnings n_errors: int = doc.getNumErrors() @@ -61,4 +61,4 @@ def validate_sbml_files(input_files : str,units_consistency: bool = False): print( f"[{error.getSeverityAsString()}] {error.getShortMessage()} | {error.getMessage()}" ) - print("-" * 80) \ No newline at end of file + print("-" * 80) diff --git a/tests/sbml/test_sbml.py b/tests/sbml/test_sbml.py index 51609f624..297e9c530 100755 --- a/tests/sbml/test_sbml.py +++ b/tests/sbml/test_sbml.py @@ -2,5 +2,5 @@ from pyneuroml import sbml -fname = "test_data/test_doc.sbml" +fname = "tests/sbml/test_data/test_doc.sbml" doc = sbml.validate_sbml_files(fname) From 883b2cac6ce628fc463791b952787984488bfa9f Mon Sep 17 00:00:00 2001 From: Robert Vickerstaff Date: Mon, 30 Oct 2023 10:29:52 +0000 Subject: [PATCH 6/6] pyneuroml/pynml.py changed by black --- pyneuroml/pynml.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/pyneuroml/pynml.py b/pyneuroml/pynml.py index da1c7f5ea..08e60c61f 100644 --- a/pyneuroml/pynml.py +++ b/pyneuroml/pynml.py @@ -2119,7 +2119,9 @@ def evaluate_arguments(args): # Deal with the SBML validation option which doesn't call run_jneuroml if args.validate_sbml: from pyneuroml.sbml import validate_sbml_files + validate_sbml_files(" ".join(args.input_files)) + return True # These do not use the shared option where files are supplied