diff --git a/bionetgen/modelapi/rulemod.py b/bionetgen/modelapi/rulemod.py index 4be1d333..8aa64957 100644 --- a/bionetgen/modelapi/rulemod.py +++ b/bionetgen/modelapi/rulemod.py @@ -1,3 +1,6 @@ +from bionetgen.core.exc import BNGParseError + + class RuleMod: """ Rule modifiers class for storage and printing. @@ -42,4 +45,6 @@ def type(self, val): if val in self.valid_mod_names or val is None: self._type = val else: - print(f"Rule modifier type {val} is not a valid type") + raise BNGParseError( + message=f": Rule modifier type {val} is not a valid type" + ) diff --git a/bionetgen/modelapi/structs.py b/bionetgen/modelapi/structs.py index da900e8f..3e7e49e8 100644 --- a/bionetgen/modelapi/structs.py +++ b/bionetgen/modelapi/structs.py @@ -69,7 +69,7 @@ def line_label(self, val) -> None: try: ll = int(val) self._line_label = "{} ".format(ll) - except: + except (TypeError, ValueError): self._line_label = "{}: ".format(val) def print_line(self) -> str: @@ -423,7 +423,12 @@ def set_rate_constants(self, rate_cts): self.rate_constants = [rate_cts[0], rate_cts[1]] self.bidirectional = True else: - print("1 or 2 rate constants allowed") + raise BNGParseError( + message=( + f": Rule {self.name} requires 1 or 2 rate constants, " + f"got {len(rate_cts)}" + ) + ) def gen_string(self): if self.bidirectional: diff --git a/bionetgen/network/networkparser.py b/bionetgen/network/networkparser.py index b131af93..bf53d4e5 100644 --- a/bionetgen/network/networkparser.py +++ b/bionetgen/network/networkparser.py @@ -1,4 +1,6 @@ import re, os +from bionetgen.core.exc import BNGParseError +from bionetgen.core.utils.logging import BNGLogger from bionetgen.main import BioNetGen from bionetgen.network.blocks import ( NetworkGroupBlock, @@ -16,6 +18,7 @@ app.setup() conf = app.config["bionetgen"] def_bng_path = conf["bngpath"] +logger = BNGLogger() class BNGNetworkParser: @@ -92,16 +95,25 @@ def parse_network(self, network_obj) -> None: spec_block = NetworkSpeciesBlock() for iline in range(sblock[0] + 1, sblock[1]): m = re.match("([^#]*)(#.*)?", self.network_lines[iline]) - if m.group(1).strip() != "": - splt = m.group(1).split() + if m is None: + continue + line_text = m.group(1) or "" + if line_text.strip() != "": + splt = line_text.split() + if len(splt) < 3: + msg = ( + f"Malformed species line at {self.path}:{iline + 1}; " + "expected ' ', " + f"got {line_text.strip()!r}" + ) + logger.error( + msg, + loc=f"{__file__} : BNGNetworkParser.parse_network()", + ) + raise BNGParseError(self.path, message=f": {msg}") sid = splt[0] name = splt[1] - try: - count = splt[2] - except: - import IPython - - IPython.embed() + count = splt[2] spec_block.add_species(sid, name, count) network_obj.add_block(spec_block) # add reactions @@ -137,4 +149,3 @@ def parse_network(self, network_obj) -> None: comment = m.group(2) grps_block.add_group(rid, name, members, comment=comment) network_obj.add_block(grps_block) - # import IPython,sys;IPython.embed();sys.exit() diff --git a/bionetgen/network/structs.py b/bionetgen/network/structs.py index e2c27e9b..d07fa75d 100644 --- a/bionetgen/network/structs.py +++ b/bionetgen/network/structs.py @@ -69,7 +69,7 @@ def line_label(self, val) -> None: try: ll = int(val) self._line_label = "{} ".format(ll) - except: + except (TypeError, ValueError): self._line_label = "{}: ".format(val) def print_line(self) -> str: diff --git a/tests/test_networkparser_structs_errors.py b/tests/test_networkparser_structs_errors.py new file mode 100644 index 00000000..de710c57 --- /dev/null +++ b/tests/test_networkparser_structs_errors.py @@ -0,0 +1,88 @@ +from unittest.mock import patch + +import pytest + +from bionetgen.core.exc import BNGParseError +from bionetgen.modelapi.rulemod import RuleMod +from bionetgen.modelapi.structs import Parameter, Rule +from bionetgen.network.structs import NetworkObj + + +class FakePattern: + def __init__(self, text): + self._text = text + + def __str__(self): + return self._text + + +NET_MALFORMED_SPECIES = """\ +# NET file +begin species + 1 A(b) +end species +""" + + +def test_networkparser_malformed_species_line_raises_parse_error(tmp_path): + net_file = tmp_path / "bad_species.net" + net_file.write_text(NET_MALFORMED_SPECIES) + from bionetgen.network import networkparser as networkparser_module + from bionetgen.network.network import Network + + with patch.object(networkparser_module, "logger") as mock_logger: + with pytest.raises(BNGParseError, match="Malformed species line"): + Network(str(net_file)) + + mock_logger.error.assert_called_once() + error_args, error_kwargs = mock_logger.error.call_args + assert "Malformed species line" in error_args[0] + assert "expected ' '" in error_args[0] + assert "bad_species.net:3" in error_args[0] + assert "BNGNetworkParser.parse_network()" in error_kwargs["loc"] + + +def test_model_struct_line_label_none_uses_string_fallback(): + parameter = Parameter("k1", "1") + parameter.line_label = None + assert parameter.line_label == "None: " + + +def test_network_struct_line_label_none_uses_string_fallback(): + obj = NetworkObj() + obj.line_label = None + assert obj.line_label == "None: " + + +def test_rule_set_rate_constants_invalid_length_raises_parse_error(): + rule = Rule( + name="r", + reactants=[FakePattern("A()")], + products=[FakePattern("B()")], + rate_constants=("k1",), + ) + with pytest.raises(BNGParseError, match="1 or 2 rate constants"): + rule.set_rate_constants(("k1", "k2", "k3")) + assert rule.bidirectional is False + assert rule.rate_constants == ["k1"] + + +def test_rule_init_without_rate_constants_raises_parse_error(): + with pytest.raises(BNGParseError, match="1 or 2 rate constants"): + Rule( + name="r", + reactants=[FakePattern("A()")], + products=[FakePattern("B()")], + ) + + +def test_rulemod_invalid_init_raises_parse_error(): + with pytest.raises(BNGParseError, match="Rule modifier type InvalidMod"): + RuleMod(mod_type="InvalidMod") + + +def test_rulemod_invalid_setter_raises_parse_error(): + rule_mod = RuleMod() + with pytest.raises(BNGParseError, match="Rule modifier type BadType"): + rule_mod.type = "BadType" + assert rule_mod.type is None