From aaddfa857743685b9a0695e63e1586d60993d233 Mon Sep 17 00:00:00 2001 From: "Ankur Sinha (Ankur Sinha Gmail)" Date: Fri, 2 Dec 2022 12:47:27 +0000 Subject: [PATCH 01/12] feat(nml.py): add level 2 validation for SegmentGroup This also sets up a general framework for us to add more tests in libNeuroML. --- neuroml/nml/generatedssupersuper.py | 21 +++++++++++++++++++++ neuroml/test/test_nml.py | 13 +++++++++++++ 2 files changed, 34 insertions(+) diff --git a/neuroml/nml/generatedssupersuper.py b/neuroml/nml/generatedssupersuper.py index a3c12dc..3a2b0af 100644 --- a/neuroml/nml/generatedssupersuper.py +++ b/neuroml/nml/generatedssupersuper.py @@ -393,6 +393,10 @@ def validate(self, recursive=False): if getattr(c, "validate_", None): v = c.validate_(self, collector, recursive) valid = valid and v + # l2 tests for specific classes + if c.__name__ == "SegmentGroup": + v = self.__l2_validate_SegmentGroup(collector) + valid = valid and v if valid is False: err = "Validation failed:\n" @@ -400,6 +404,23 @@ def validate(self, recursive=False): err += f"- {msg}\n" raise ValueError(err) + def __l2_validate_SegmentGroup(self, collector): + """Additional validation tests for SegmentGroup class. + + :param collector: GdsCollector instance + :type collector: GdsCollector + :returns: False if validation fails, True otherwise + :rtype: bool + """ + # T1: segment group includes itself + for sginc in self.includes: + print(f"{sginc.segment_groups}, {self.id}") + if sginc.segment_groups == self.id: + collector.add_message("Segment group includes itself.") + return False + + return True + def parentinfo(self, return_format="string"): """Show the list of possible parents. diff --git a/neuroml/test/test_nml.py b/neuroml/test/test_nml.py index 8f2caa2..17021d4 100644 --- a/neuroml/test/test_nml.py +++ b/neuroml/test/test_nml.py @@ -721,3 +721,16 @@ def test_morphinfo(self): cell.morphinfo(True) cell.biophysinfo() + + def test_l2_validate_SegmentGroup(self): + """Test __l2_validate_Cell + + :returns: None + + """ + tg = component_factory("SegmentGroup", id="test_group") # type: neuroml.SegmentGroup + + # should throw value error on post addition validation + with self.assertRaises(ValueError) as cm: + tg.add(neuroml.Include(segment_groups="test_group")) + print(cm.exception) From 8b2eaa0c531a289d150d1ec9d9f785a378fc782f Mon Sep 17 00:00:00 2001 From: "Ankur Sinha (Ankur Sinha Gmail)" Date: Fri, 3 Feb 2023 15:53:04 +0000 Subject: [PATCH 02/12] feat(l2testing): add framework for level 2 validation tests --- neuroml/l2validators.py | 132 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 132 insertions(+) create mode 100644 neuroml/l2validators.py diff --git a/neuroml/l2validators.py b/neuroml/l2validators.py new file mode 100644 index 0000000..3d7017d --- /dev/null +++ b/neuroml/l2validators.py @@ -0,0 +1,132 @@ +#!/usr/bin/env python3 +""" +"Level 2" validators: extra tests in addition to the validation tests included +in the standard/schema + +File: neuroml/l2validators.py + +Copyright 2023 NeuroML contributors +Author: Ankur Sinha +""" + +import inspect +import importlib +import typing +from abc import ABC, abstractmethod +from enum import Enum +import logging +logger = logging.getLogger(__name__) + + +class TEST_LEVEL(Enum): + WARNING = logging.WARNING + ERROR = logging.ERROR + + +class StandardTestSuper(ABC): + + """Class implementing a standard test. + + All tests should extend this class. `L2Validator` will automatically + register any classes in this module that extend this class. + """ + + test_id = None + target_class = None + description = None + level = None + + @abstractmethod + def run(self, obj): + """Implementation of test to run. + :param obj: object to run test on + :returns: True if test passes, false if not + """ + pass + + +class L2Validator(object): + + """Main validator class.""" + tests: typing.Dict[str, typing.Any] = {} + + def __init__(self): + """Register all classes that extend StandardTestSuper """ + this_module = importlib.import_module(__name__) + for name, obj in inspect.getmembers(this_module): + if inspect.isclass(obj): + if StandardTestSuper in obj.__mro__: + if obj != StandardTestSuper: + self.register_test(obj) + + @classmethod + def validate(cls, obj, collector=None): + """Main validate method that should include calls to all tests that are + to be run on an object + + :param obj: object to be validated + :type obj: an object to be validated + :param collector: a GdsCollector instance for messages + :type collector: neuroml.GdsCollector + :returns: True if all validation tests pass, false if not + """ + for test in cls.tests[obj.__class__.__name__]: + if not test.run(obj): + if collector: + collector.add_message(f"Validation failed: {test.test_id}: {test.description}") + else: + if test.level == logging.WARN: + logger.warn(f"Validation failed: {test.test_id}: {test.description}") + else: + logger.error(f"Validation failed: {test.test_id}: {test.description}") + return False + + return True + + @classmethod + def register_test(cls, test): + """Register a test class + + :param test: test class to register + :returns: None + + """ + try: + if test not in cls.tests[test.target_class]: + cls.tests[test.target_class].append(test) + except KeyError: + cls.tests[test.target_class] = [test] + + @classmethod + def list_tests(cls): + """List all registered tests.""" + print("Registered tests:") + for key, val in cls.tests.items(): + print(f"* {key}") + for t in val: + print(f"\t* {t.test_id}: {t.description}") + print() + + +class SegmentGroupSelfIncludes(StandardTestSuper): + + """Segment groups should not include themselves""" + test_id = "0001" + target_class = "SegmentGroup" + description = "Segment group includes itself" + level = TEST_LEVEL.ERROR + + @classmethod + def run(self, obj): + """Test runner method. + + :param obj: object to run tests on + :type object: any neuroml.* object + :returns: True if test passes, false if not. + + """ + for sginc in obj.includes: + print(f"{sginc.segment_groups}, {obj.id}") + if sginc.segment_groups == obj.id: + return False + return True From 87c2461149c1d71fd355a4cd0e7b06c96b3c8d90 Mon Sep 17 00:00:00 2001 From: "Ankur Sinha (Ankur Sinha Gmail)" Date: Fri, 3 Feb 2023 15:53:25 +0000 Subject: [PATCH 03/12] test(l2validators): add initial set of tests --- neuroml/test/test_l2validators.py | 97 +++++++++++++++++++++++++++++++ 1 file changed, 97 insertions(+) create mode 100644 neuroml/test/test_l2validators.py diff --git a/neuroml/test/test_l2validators.py b/neuroml/test/test_l2validators.py new file mode 100644 index 0000000..5419406 --- /dev/null +++ b/neuroml/test/test_l2validators.py @@ -0,0 +1,97 @@ +#!/usr/bin/env python3 +""" +Enter one line description here. + +File: + +Copyright 2023 Ankur Sinha +Author: Ankur Sinha +""" + +try: + import unittest2 as unittest +except ImportError: + import unittest + + +from neuroml.l2validators import (L2Validator, SegmentGroupSelfIncludes, + StandardTestSuper) + + +class TestL2Validators(unittest.TestCase): + + """Docstring for TestL2Validators. """ + + def test_l2validator(self): + """Test L2Validtor + + tests that standard tests defined in the l2validators module are + automatically registered. + """ + + self.l2validator = None + self.l2validator = L2Validator() + self.l2validator.list_tests() + self.assertIn(SegmentGroupSelfIncludes, list(self.l2validator.tests["SegmentGroup"])) + + def test_l2validator_register(self): + """Test l2validator test registration """ + class DummyTest(StandardTestSuper): + + """A dummy test""" + test_id = "0001" + target_class = "DummyClass" + description = "DummyClass" + level = 0 + + @classmethod + def run(self, obj): + """Test runner method. + + :param obj: object to run tests on + :type object: any neuroml.* object + :returns: True if test passes, false if not. + + """ + return True + + self.l2validator = None + self.l2validator = L2Validator() + self.l2validator.register_test(DummyTest) + self.l2validator.list_tests() + self.assertIn(DummyTest, list(self.l2validator.tests["DummyClass"])) + + def test_l2validator_runs(self): + """Test l2validator test running""" + class DummyTest(StandardTestSuper): + + """A dummy test""" + test_id = "0001" + target_class = "DummyClass" + description = "DummyClass" + level = 0 + + @classmethod + def run(self, obj): + """Test runner method. + + :param obj: object to run tests on + :type object: any neuroml.* object + :returns: True if test passes, false if not. + + """ + return True + + class DummyClass(object): + + """A dummy class""" + + name = "dummy" + + self.l2validator = None + self.l2validator = L2Validator() + self.l2validator.register_test(DummyTest) + dummy = DummyClass() + + self.assertIn(DummyTest, list(self.l2validator.tests["DummyClass"])) + self.assertTrue(self.l2validator.validate(dummy)) From c9feacbce73fd4322b51ee402f4cd69442015f95 Mon Sep 17 00:00:00 2001 From: "Ankur Sinha (Ankur Sinha Gmail)" Date: Fri, 3 Feb 2023 15:53:56 +0000 Subject: [PATCH 04/12] feat(l2validator): remove duplicate SegmentGroup validation test --- neuroml/nml/generatedssupersuper.py | 22 ++++------------------ neuroml/test/test_nml.py | 12 ------------ 2 files changed, 4 insertions(+), 30 deletions(-) diff --git a/neuroml/nml/generatedssupersuper.py b/neuroml/nml/generatedssupersuper.py index a7c424a..d7a35ea 100644 --- a/neuroml/nml/generatedssupersuper.py +++ b/neuroml/nml/generatedssupersuper.py @@ -11,6 +11,7 @@ import sys from .generatedscollector import GdsCollector +from .l2validators import L2Validator class GeneratedsSuperSuper(object): @@ -20,6 +21,8 @@ class GeneratedsSuperSuper(object): Any bits that must go into every libNeuroML class should go here. """ + l2validator = L2Validator() + def add(self, obj=None, hint=None, force=False, validate=True, **kwargs): """Generic function to allow easy addition of a new member to a NeuroML object. Without arguments, when `obj=None`, it simply calls the `info()` method @@ -395,7 +398,7 @@ def validate(self, recursive=False): valid = valid and v # l2 tests for specific classes if c.__name__ == "SegmentGroup": - v = self.__l2_validate_SegmentGroup(collector) + v = self.l2validator.validate(self, collector) valid = valid and v if valid is False: @@ -404,23 +407,6 @@ def validate(self, recursive=False): err += f"- {msg}\n" raise ValueError(err) - def __l2_validate_SegmentGroup(self, collector): - """Additional validation tests for SegmentGroup class. - - :param collector: GdsCollector instance - :type collector: GdsCollector - :returns: False if validation fails, True otherwise - :rtype: bool - """ - # T1: segment group includes itself - for sginc in self.includes: - print(f"{sginc.segment_groups}, {self.id}") - if sginc.segment_groups == self.id: - collector.add_message("Segment group includes itself.") - return False - - return True - def parentinfo(self, return_format="string"): """Show the list of possible parents. diff --git a/neuroml/test/test_nml.py b/neuroml/test/test_nml.py index 17021d4..165275b 100644 --- a/neuroml/test/test_nml.py +++ b/neuroml/test/test_nml.py @@ -722,15 +722,3 @@ def test_morphinfo(self): cell.morphinfo(True) cell.biophysinfo() - def test_l2_validate_SegmentGroup(self): - """Test __l2_validate_Cell - - :returns: None - - """ - tg = component_factory("SegmentGroup", id="test_group") # type: neuroml.SegmentGroup - - # should throw value error on post addition validation - with self.assertRaises(ValueError) as cm: - tg.add(neuroml.Include(segment_groups="test_group")) - print(cm.exception) From f42bdebce3531bf68b7dc46db825dbee01e27c02 Mon Sep 17 00:00:00 2001 From: "Ankur Sinha (Ankur Sinha Gmail)" Date: Fri, 3 Feb 2023 17:25:05 +0000 Subject: [PATCH 05/12] feat(l2validator): allow validation of objects by arbitrary test classes This is so that an object of class A may also be tested with tests defined for its ancestors. It is the developer's responsibility to ensure that only the right tests are run for a particular object. With great power, comes great responsibility! --- neuroml/l2validators.py | 52 ++++++++++++++++++++--------- neuroml/nml/generatedssupersuper.py | 12 +++---- 2 files changed, 42 insertions(+), 22 deletions(-) diff --git a/neuroml/l2validators.py b/neuroml/l2validators.py index 3d7017d..31e5408 100644 --- a/neuroml/l2validators.py +++ b/neuroml/l2validators.py @@ -31,10 +31,10 @@ class StandardTestSuper(ABC): register any classes in this module that extend this class. """ - test_id = None - target_class = None - description = None - level = None + test_id = "" + target_class = "" + description = "" + level = TEST_LEVEL.ERROR @abstractmethod def run(self, obj): @@ -60,28 +60,49 @@ def __init__(self): self.register_test(obj) @classmethod - def validate(cls, obj, collector=None): + def validate(cls, obj, class_name=None, collector=None): """Main validate method that should include calls to all tests that are to be run on an object :param obj: object to be validated :type obj: an object to be validated + :param class_name: name of class for which tests are to be run + In most cases, this will be None, and the class name will be + obtained from the object. However, in cases where a tests has been + defined in an ancestor (BaseCell, which a Cell would inherit), one + can pass the class name of the ancestor. This can be used to run L2 + test defined for ancestors for all descendents. In fact, tests for + arbitrary classes can be run on any objects. It is for the + developer to ensure that the appropriate tests are run. + :type class_name: str :param collector: a GdsCollector instance for messages :type collector: neuroml.GdsCollector :returns: True if all validation tests pass, false if not """ - for test in cls.tests[obj.__class__.__name__]: - if not test.run(obj): - if collector: - collector.add_message(f"Validation failed: {test.test_id}: {test.description}") + test_result = True + class_name_ = class_name if class_name else obj.__class__.__name__ + # The collector looks for a local with name "self" in the stack frame + # to figure out what the "caller" class is. + # So, set "self" to the object that is being validated here. + self = obj + self._gds_collector = collector + + try: + for test in cls.tests[class_name_]: + test_result = test.run(obj) + except KeyError: + pass # no L2 tests have been defined + + if test_result is False: + if self._gds_collector: + self._gds_collector.add_message(f"Validation failed: {test.test_id}: {test.description}") + else: + if test.level == logging.WARNING: + logger.warn(f"Validation failed: {test.test_id}: {test.description}") else: - if test.level == logging.WARN: - logger.warn(f"Validation failed: {test.test_id}: {test.description}") - else: - logger.error(f"Validation failed: {test.test_id}: {test.description}") - return False + logger.error(f"Validation failed: {test.test_id}: {test.description}") - return True + return test_result @classmethod def register_test(cls, test): @@ -126,7 +147,6 @@ def run(self, obj): """ for sginc in obj.includes: - print(f"{sginc.segment_groups}, {obj.id}") if sginc.segment_groups == obj.id: return False return True diff --git a/neuroml/nml/generatedssupersuper.py b/neuroml/nml/generatedssupersuper.py index d7a35ea..b5a1d12 100644 --- a/neuroml/nml/generatedssupersuper.py +++ b/neuroml/nml/generatedssupersuper.py @@ -11,7 +11,7 @@ import sys from .generatedscollector import GdsCollector -from .l2validators import L2Validator +from ..l2validators import L2Validator class GeneratedsSuperSuper(object): @@ -21,7 +21,7 @@ class GeneratedsSuperSuper(object): Any bits that must go into every libNeuroML class should go here. """ - l2validator = L2Validator() + l2_validator = L2Validator() def add(self, obj=None, hint=None, force=False, validate=True, **kwargs): """Generic function to allow easy addition of a new member to a NeuroML object. @@ -396,10 +396,10 @@ def validate(self, recursive=False): if getattr(c, "validate_", None): v = c.validate_(self, collector, recursive) valid = valid and v - # l2 tests for specific classes - if c.__name__ == "SegmentGroup": - v = self.l2validator.validate(self, collector) - valid = valid and v + + # l2 tests for specific classes + v1 = self.l2_validator.validate(obj=self, class_name=c.__name__, collector=collector) + valid = valid and v1 if valid is False: err = "Validation failed:\n" From 9260309e7633cfd26029cf3c31fc8e99f5353bf9 Mon Sep 17 00:00:00 2001 From: "Ankur Sinha (Ankur Sinha Gmail)" Date: Fri, 3 Feb 2023 17:26:16 +0000 Subject: [PATCH 06/12] test(l2validator): add test for segment group including itself --- neuroml/test/test_l2validators.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/neuroml/test/test_l2validators.py b/neuroml/test/test_l2validators.py index 5419406..bd882d6 100644 --- a/neuroml/test/test_l2validators.py +++ b/neuroml/test/test_l2validators.py @@ -16,6 +16,8 @@ from neuroml.l2validators import (L2Validator, SegmentGroupSelfIncludes, StandardTestSuper) +from neuroml import SegmentGroup, Include +from neuroml.utils import component_factory class TestL2Validators(unittest.TestCase): @@ -95,3 +97,10 @@ class DummyClass(object): self.assertIn(DummyTest, list(self.l2validator.tests["DummyClass"])) self.assertTrue(self.l2validator.validate(dummy)) + + def test_SegmentGroupSelfIncludes(self): + """test SegmentGroupSelfIncludes class""" + sg = component_factory(SegmentGroup, validate=True, id="dummy_group") + sg.l2_validator.list_tests() + with self.assertRaises(ValueError): + sg.add(Include, segment_groups="dummy_group") From 46893e33e4239dcdbc75325f4f9a2069b290e8f1 Mon Sep 17 00:00:00 2001 From: "Ankur Sinha (Ankur Sinha Gmail)" Date: Fri, 3 Feb 2023 17:51:31 +0000 Subject: [PATCH 07/12] fix(l2validator): pass tests that are marked as warnings --- neuroml/l2validators.py | 22 +++++++++++++--------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/neuroml/l2validators.py b/neuroml/l2validators.py index 31e5408..b4ed63e 100644 --- a/neuroml/l2validators.py +++ b/neuroml/l2validators.py @@ -90,17 +90,21 @@ def validate(cls, obj, class_name=None, collector=None): try: for test in cls.tests[class_name_]: test_result = test.run(obj) - except KeyError: - pass # no L2 tests have been defined - if test_result is False: - if self._gds_collector: - self._gds_collector.add_message(f"Validation failed: {test.test_id}: {test.description}") - else: - if test.level == logging.WARNING: - logger.warn(f"Validation failed: {test.test_id}: {test.description}") + if test_result is False: + if self._gds_collector: + self._gds_collector.add_message(f"Validation failed: {test.test_id}: {test.description}") + if test.level == logging.WARNING: + # a warning, don't mark as invalid + test_result = True + logger.warning(f"Validation failed: {obj}: {test.test_id}: {test.description}") + else: + logger.error(f"Validation failed: {obj}: {test.test_id}: {test.description}") else: - logger.error(f"Validation failed: {test.test_id}: {test.description}") + logger.debug(f"PASSED: {obj}: {test.test_id}: {test.description}") + + except KeyError: + pass # no L2 tests have been defined return test_result From e5088fd5cee45e65fea0e5b4f3e23974a4502b3a Mon Sep 17 00:00:00 2001 From: "Ankur Sinha (Ankur Sinha Gmail)" Date: Fri, 3 Feb 2023 17:52:13 +0000 Subject: [PATCH 08/12] feat(generatedssuper): print out remaining collected messsages When a validation test fails, we error immediately and print out the validation error. However, if a test that's marked for warnings, not errors fails, we don't error immediately, but we do print out the collected warnings. --- neuroml/nml/generatedssupersuper.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/neuroml/nml/generatedssupersuper.py b/neuroml/nml/generatedssupersuper.py index b5a1d12..5c5092b 100644 --- a/neuroml/nml/generatedssupersuper.py +++ b/neuroml/nml/generatedssupersuper.py @@ -407,6 +407,14 @@ def validate(self, recursive=False): err += f"- {msg}\n" raise ValueError(err) + # Other validation warnings + msgs = collector.get_messages() + if len(msgs) > 0: + err = "Validation warnings:\n" + for msg in collector.get_messages(): + err += f"- {msg}\n" + print(err) + def parentinfo(self, return_format="string"): """Show the list of possible parents. From b19c94d574b9c1d92c0200065343e0535e54f1a7 Mon Sep 17 00:00:00 2001 From: "Ankur Sinha (Ankur Sinha Gmail)" Date: Fri, 3 Feb 2023 17:53:29 +0000 Subject: [PATCH 09/12] chore(cell-helper): add todo to deduplicate based on contents also --- neuroml/nml/helper_methods.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/neuroml/nml/helper_methods.py b/neuroml/nml/helper_methods.py index 8a5f2f7..281100c 100644 --- a/neuroml/nml/helper_methods.py +++ b/neuroml/nml/helper_methods.py @@ -1762,6 +1762,8 @@ def optimise_segment_group(self, seg_group_id): members = new_members seg_group.members = list(members) + # TODO: only deduplicates by identical objects, also deduplicate by + # contents includes = seg_group.includes new_includes = [] for i in includes: From 14c45545fc74dda6aa3add596a9abe36c7d5368c Mon Sep 17 00:00:00 2001 From: "Ankur Sinha (Ankur Sinha Gmail)" Date: Fri, 3 Feb 2023 17:54:16 +0000 Subject: [PATCH 10/12] test(l2validator): add more tests --- neuroml/test/test_l2validators.py | 78 ++++++++++++++++++++++++++++++- 1 file changed, 76 insertions(+), 2 deletions(-) diff --git a/neuroml/test/test_l2validators.py b/neuroml/test/test_l2validators.py index bd882d6..8d90970 100644 --- a/neuroml/test/test_l2validators.py +++ b/neuroml/test/test_l2validators.py @@ -12,6 +12,7 @@ import unittest2 as unittest except ImportError: import unittest +import logging from neuroml.l2validators import (L2Validator, SegmentGroupSelfIncludes, @@ -19,7 +20,6 @@ from neuroml import SegmentGroup, Include from neuroml.utils import component_factory - class TestL2Validators(unittest.TestCase): """Docstring for TestL2Validators. """ @@ -71,7 +71,7 @@ class DummyTest(StandardTestSuper): test_id = "0001" target_class = "DummyClass" description = "DummyClass" - level = 0 + level = logging.ERROR @classmethod def run(self, obj): @@ -98,6 +98,80 @@ class DummyClass(object): self.assertIn(DummyTest, list(self.l2validator.tests["DummyClass"])) self.assertTrue(self.l2validator.validate(dummy)) + def test_l2validator_runs_errors(self): + """Test l2validator test running""" + class DummyTest(StandardTestSuper): + + """A dummy test""" + test_id = "0001" + target_class = "DummyClass" + description = "DummyClass" + level = logging.ERROR + + @classmethod + def run(self, obj): + """Test runner method. + + :param obj: object to run tests on + :type object: any neuroml.* object + :returns: True if test passes, false if not. + + """ + return False + + class DummyClass(object): + + """A dummy class""" + + name = "dummy" + + self.l2validator = None + self.l2validator = L2Validator() + self.l2validator.register_test(DummyTest) + dummy = DummyClass() + + self.assertIn(DummyTest, list(self.l2validator.tests["DummyClass"])) + + # since it's an error, validation should fail + self.assertFalse(self.l2validator.validate(dummy)) + + def test_l2validator_runs_warns(self): + """Test l2validator test running""" + class DummyTest(StandardTestSuper): + + """A dummy test""" + test_id = "0001" + target_class = "DummyClass" + description = "DummyClass" + level = logging.WARNING + + @classmethod + def run(self, obj): + """Test runner method. + + :param obj: object to run tests on + :type object: any neuroml.* object + :returns: True if test passes, false if not. + + """ + return False + + class DummyClass(object): + + """A dummy class""" + + name = "dummy" + + self.l2validator = None + self.l2validator = L2Validator() + self.l2validator.register_test(DummyTest) + dummy = DummyClass() + + self.assertIn(DummyTest, list(self.l2validator.tests["DummyClass"])) + + # since it's a warning, validation should still pass + self.assertTrue(self.l2validator.validate(dummy)) + def test_SegmentGroupSelfIncludes(self): """test SegmentGroupSelfIncludes class""" sg = component_factory(SegmentGroup, validate=True, id="dummy_group") From cd28d71812319a0ebbb86ac7f710a7621d883fd9 Mon Sep 17 00:00:00 2001 From: "Ankur Sinha (Ankur Sinha Gmail)" Date: Fri, 3 Feb 2023 17:54:33 +0000 Subject: [PATCH 11/12] fix(test-cell-optimise): correctly add duplicates of same object The test was wrong, but not sure why it passed before. Each new Include object is completely different, and would not count as a duplicate. TODO: check this again and pin down why behaviour changed after addition of l2validator test --- neuroml/test/test_nml.py | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/neuroml/test/test_nml.py b/neuroml/test/test_nml.py index 165275b..d0c0421 100644 --- a/neuroml/test/test_nml.py +++ b/neuroml/test/test_nml.py @@ -609,20 +609,19 @@ def test_optimise_segment_group(self): segments=cell.get_segment_group("soma_0").members[0].segments)) # add group to all, explicitly - cell.get_segment_group("all").add(neuroml.Include, - segment_groups="soma_0", - force=True) - cell.get_segment_group("all").add(neuroml.Include, - segment_groups="soma_0", - force=True) - cell.get_segment_group("all").add(neuroml.Include, - segment_groups="soma_0", - force=True) + inc = neuroml.Include(segment_groups="soma_0", force=True) + + cell.get_segment_group("all").add(inc, force=True) + cell.get_segment_group("all").add(inc, force=True) + cell.get_segment_group("all").add(inc, force=True) + self.assertEqual(4, len(cell.get_segment_group("all").includes)) # should have only one included segment group # should have no segments, because the segment is included in the one # segment group already cell.optimise_segment_group("all") + for i in cell.get_segment_group("all").includes: + print(i) self.assertEqual(1, len(cell.get_segment_group("all").includes)) def test_create_unbranched_segment_group_branches(self): From 518f2947552eebf10f1ff8bd9b8b324d32e13185 Mon Sep 17 00:00:00 2001 From: "Ankur Sinha (Ankur Sinha Gmail)" Date: Thu, 9 Feb 2023 10:39:10 +0000 Subject: [PATCH 12/12] WIP: performance drops need investigation --- neuroml/l2validators.py | 7 +++---- neuroml/nml/generatedssupersuper.py | 15 +++++++++------ 2 files changed, 12 insertions(+), 10 deletions(-) diff --git a/neuroml/l2validators.py b/neuroml/l2validators.py index b4ed63e..e8f61ca 100644 --- a/neuroml/l2validators.py +++ b/neuroml/l2validators.py @@ -84,16 +84,15 @@ def validate(cls, obj, class_name=None, collector=None): # The collector looks for a local with name "self" in the stack frame # to figure out what the "caller" class is. # So, set "self" to the object that is being validated here. - self = obj - self._gds_collector = collector + # self = obj try: for test in cls.tests[class_name_]: test_result = test.run(obj) if test_result is False: - if self._gds_collector: - self._gds_collector.add_message(f"Validation failed: {test.test_id}: {test.description}") + if obj.collector: + obj.collector.add_message(f"Validation failed: {test.test_id}: {test.description}") if test.level == logging.WARNING: # a warning, don't mark as invalid test_result = True diff --git a/neuroml/nml/generatedssupersuper.py b/neuroml/nml/generatedssupersuper.py index 5c5092b..1ace97a 100644 --- a/neuroml/nml/generatedssupersuper.py +++ b/neuroml/nml/generatedssupersuper.py @@ -22,6 +22,7 @@ class GeneratedsSuperSuper(object): """ l2_validator = L2Validator() + collector = GdsCollector() # noqa def add(self, obj=None, hint=None, force=False, validate=True, **kwargs): """Generic function to allow easy addition of a new member to a NeuroML object. @@ -390,28 +391,30 @@ def validate(self, recursive=False): :rtype: None :raises ValueError: if component is invalid """ - collector = GdsCollector() # noqa + self.collector.clear_messages() valid = True for c in type(self).__mro__: if getattr(c, "validate_", None): - v = c.validate_(self, collector, recursive) + v = c.validate_(self, self.collector, recursive) valid = valid and v # l2 tests for specific classes - v1 = self.l2_validator.validate(obj=self, class_name=c.__name__, collector=collector) + v1 = self.l2_validator.validate(obj=self, + class_name=c.__name__, + collector=self.collector) valid = valid and v1 if valid is False: err = "Validation failed:\n" - for msg in collector.get_messages(): + for msg in self.collector.get_messages(): err += f"- {msg}\n" raise ValueError(err) # Other validation warnings - msgs = collector.get_messages() + msgs = self.collector.get_messages() if len(msgs) > 0: err = "Validation warnings:\n" - for msg in collector.get_messages(): + for msg in self.collector.get_messages(): err += f"- {msg}\n" print(err)