From 09fdbc52a42c0a7dcee143a205cfed450510bc31 Mon Sep 17 00:00:00 2001 From: "Ankur Sinha (Ankur Sinha Gmail)" Date: Thu, 30 May 2024 18:12:39 +0100 Subject: [PATCH 1/3] feat(validation): enable global toggling of validation Fixes #158 --- .pre-commit-config.yaml | 15 ++++++++ neuroml/__init__.py | 50 +++++++++++++++++++++++++- neuroml/build_time_validation.py | 25 +++++++++++++ neuroml/nml/generatedssupersuper.py | 24 ++++++++----- neuroml/test/test_global_validation.py | 38 ++++++++++++++++++++ setup.cfg | 1 + 6 files changed, 143 insertions(+), 10 deletions(-) create mode 100644 .pre-commit-config.yaml create mode 100644 neuroml/build_time_validation.py create mode 100644 neuroml/test/test_global_validation.py diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 00000000..5f83cf7e --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,15 @@ +# See https://pre-commit.com for more information +# See https://pre-commit.com/hooks.html for more hooks +repos: +- repo: https://github.com/pre-commit/pre-commit-hooks + rev: v4.4.0 + hooks: + - id: trailing-whitespace + - id: end-of-file-fixer + - id: check-added-large-files +- repo: https://github.com/astral-sh/ruff-pre-commit + rev: v0.4.1 + hooks: + - id: ruff + args: [ "--select", "I", "--fix" ] + - id: ruff-format diff --git a/neuroml/__init__.py b/neuroml/__init__.py index 1b742460..d761538a 100644 --- a/neuroml/__init__.py +++ b/neuroml/__init__.py @@ -1,7 +1,18 @@ -from .nml.nml import * # allows importation of all neuroml classes +import logging + +from . import build_time_validation from .__version__ import __version__ as __version__ from .__version__ import __version_info__ as __version__info__ from .__version__ import current_neuroml_version as current_neuroml_version +from .nml.nml import * # allows importation of all neuroml classes + +logging.basicConfig( + format="libNeuroML >>> %(levelname)s - %(message)s", + level=logging.WARN, +) + +logger = logging.getLogger(__name__) +logger.setLevel(logging.INFO) def print_(text, verbose=True): @@ -10,3 +21,40 @@ def print_(text, verbose=True): # if not isinstance(text, str): text = text.decode('ascii') if verbose: print("%s%s" % (prefix, text.replace("\n", "\n" + prefix))) + + +def disable_build_time_validation(): + """Disable build time validation. + + This will disable the validation of components when they are being created + using the factory functions, such as component_factory, and add. + + This is useful for certain cases where a new component cannot necessarily + be created in a valid state from the beginning. For example, a population + is usually created after a network, but a network without a population + would not be valid. + + This switch provides a convenient way to disable this check. + + Please note that this should only be used sparingly, and not abused to turn + off build time validation completely. + + .. versionadded:: 0.6.0 + """ + build_time_validation.ENABLED = False + logger.warning("Build time validation has been disabled.") + + +def enable_build_time_validation(): + """Enable build time validation""" + build_time_validation.ENABLED = True + logger.info("Build time validation has been enabled.") + + +def get_build_time_validation() -> bool: + """Get build time validation + + :returns: state of build time validation + :rtype: bool + """ + return build_time_validation.ENABLED diff --git a/neuroml/build_time_validation.py b/neuroml/build_time_validation.py new file mode 100644 index 00000000..2cbdd5f2 --- /dev/null +++ b/neuroml/build_time_validation.py @@ -0,0 +1,25 @@ +#!/usr/bin/env python3 +""" +Module handling the current state of the build time validation. + +Setting ENABLED to False will disable build time validation when using the +`component_factory` and `add` functions. This setting overrides the `validate` +parameter of the two functions also. + +Disabling build time validation is useful for certain cases where a new +component cannot necessarily be created in a valid state from the beginning. +For example, a population is usually created after a network, but a network +without a population would not be valid. + +Please note that this should only be used sparingly, and not abused to turn +off build time validation completely. + +Split out into a separate module so that all the parts of the code can +access/modify it without the creation of circular dependencies. + +File: build_time_validation.py + +Copyright 2024 NeuroML contributors +""" + +ENABLED = True diff --git a/neuroml/nml/generatedssupersuper.py b/neuroml/nml/generatedssupersuper.py index bbe3a020..dacfc20f 100644 --- a/neuroml/nml/generatedssupersuper.py +++ b/neuroml/nml/generatedssupersuper.py @@ -7,14 +7,18 @@ Copyright 2023 NeuroML contributors """ - +import logging import sys +import neuroml.build_time_validation + from .generatedscollector import GdsCollector +logger = logging.getLogger(__name__) +logger.setLevel(logging.INFO) -class GeneratedsSuperSuper(object): +class GeneratedsSuperSuper(object): """Super class for GeneratedsSuper. Any bits that must go into every libNeuroML class should go here. @@ -52,7 +56,7 @@ def add(self, obj=None, hint=None, force=False, validate=True, **kwargs): self.info() return # a component type has been passed, create and add a new one - if type(obj) == type or type(obj) == str: + if type(obj) is type or isinstance(obj, str): obj = self.component_factory(obj, validate=validate, **kwargs) # getattr only returns the value of the provided member but one cannot @@ -69,9 +73,7 @@ def add(self, obj=None, hint=None, force=False, validate=True, **kwargs): # no targets found e = Exception( """A member object of {} type could not be found in NeuroML class {}.\n{} - """.format( - type(obj).__name__, type(self).__name__, self.info() - ) + """.format(type(obj).__name__, type(self).__name__, self.info()) ) raise e elif len(targets) == 1: @@ -93,8 +95,10 @@ def add(self, obj=None, hint=None, force=False, validate=True, **kwargs): self.__add(obj, t, force) break - if validate: + if neuroml.build_time_validation.ENABLED and validate: self.validate() + else: + logger.warn("Build time validation is disabled.") return obj @classmethod @@ -150,8 +154,10 @@ def component_factory(cls, component_type, validate=True, **kwargs): comp._check_arg_list(**kwargs) - if validate: + if neuroml.build_time_validation.ENABLED and validate: comp.validate() + else: + logger.warn("Build time validation is disabled.") return comp def __add(self, obj, member, force=False): @@ -475,7 +481,7 @@ def parentinfo(self, return_format="string"): if ac.startswith("_") or ac.endswith("_") or ac in excluded_classes: continue cc = getattr(module_object, ac, None) - if type(cc) == type: + if type(cc) is type: try: cc_members = cc()._get_members() for amember in cc_members: diff --git a/neuroml/test/test_global_validation.py b/neuroml/test/test_global_validation.py new file mode 100644 index 00000000..ec22d0ca --- /dev/null +++ b/neuroml/test/test_global_validation.py @@ -0,0 +1,38 @@ +#!/usr/bin/env python3 +""" +Test global validation toggles + +File: test_global_validation.py + +Copyright 2024 Ankur Sinha +Author: Ankur Sinha +""" + +try: + import unittest2 as unittest +except ImportError: + import unittest + +import neuroml +from neuroml import ( + disable_build_time_validation, + enable_build_time_validation, + get_build_time_validation, +) +from neuroml.utils import component_factory + + +class TestGlobalValidationToggle(unittest.TestCase): + def test_global_validation_toggle(self): + """Test enabling and disabling build time validation""" + self.assertTrue(get_build_time_validation()) + with self.assertRaises(ValueError): + anet = component_factory(neuroml.Network, id="anet") + + disable_build_time_validation() + self.assertFalse(get_build_time_validation()) + anet = component_factory(neuroml.Network, id="anet") + + enable_build_time_validation() + with self.assertRaises(ValueError): + anet = component_factory(neuroml.Network, id="anet") diff --git a/setup.cfg b/setup.cfg index ee317b2b..9cb63425 100644 --- a/setup.cfg +++ b/setup.cfg @@ -59,6 +59,7 @@ dev = pytest ruff natsort + pre-commit doc = sphinxcontrib-bibtex From d243bca430a335791207e7bcbacac6bdbcd259d6 Mon Sep 17 00:00:00 2001 From: "Ankur Sinha (Ankur Sinha Gmail)" Date: Thu, 30 May 2024 18:13:55 +0100 Subject: [PATCH 2/3] chore: add versionadded --- neuroml/__init__.py | 7 ++++++- neuroml/build_time_validation.py | 2 ++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/neuroml/__init__.py b/neuroml/__init__.py index d761538a..b4e140b2 100644 --- a/neuroml/__init__.py +++ b/neuroml/__init__.py @@ -46,7 +46,10 @@ def disable_build_time_validation(): def enable_build_time_validation(): - """Enable build time validation""" + """Enable build time validation + + .. versionadded:: 0.6.0 + """ build_time_validation.ENABLED = True logger.info("Build time validation has been enabled.") @@ -54,6 +57,8 @@ def enable_build_time_validation(): def get_build_time_validation() -> bool: """Get build time validation + .. versionadded:: 0.6.0 + :returns: state of build time validation :rtype: bool """ diff --git a/neuroml/build_time_validation.py b/neuroml/build_time_validation.py index 2cbdd5f2..eacb7013 100644 --- a/neuroml/build_time_validation.py +++ b/neuroml/build_time_validation.py @@ -17,6 +17,8 @@ Split out into a separate module so that all the parts of the code can access/modify it without the creation of circular dependencies. +.. versionadded:: 0.6.0 + File: build_time_validation.py Copyright 2024 NeuroML contributors From 16d2d9112b2929d74604a0d14125235fc4bb29ec Mon Sep 17 00:00:00 2001 From: "Ankur Sinha (Ankur Sinha Gmail)" Date: Thu, 30 May 2024 18:19:55 +0100 Subject: [PATCH 3/3] chore: add contributing doc [skip ci] --- CONTRIBUTING.md | 112 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 112 insertions(+) create mode 100644 CONTRIBUTING.md diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 00000000..79416054 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,112 @@ +# Contributing + +Please open issues to discuss enhancements and bugs that you may encounter with +libNeuroML. Pull requests with enhancements and bug fixes are welcome. + +## Virtual environments and editable installs + +It is best to use [virtual environments](https://docs.python.org/3/tutorial/venv.html) when developing Python packages. +This ensures that one uses a clean environment that includes the necessary +dependencies and does not affect the overall system installation. + +For quick development, consider using [editable installs](https://setuptools.pypa.io/en/latest/userguide/development_mode.html). +The dependencies are broken down in the `setup.cfg` file. To get a complete development environment, one can run: + + + pip install -e .[dev] # an editable install with all development dependecies installed + + +## Code style + +1. The source code uses spaces, and each tab is equivalent to 4 spaces. + +2. We use the [reStructuredText (reST) + format](https://stackoverflow.com/a/24385103/375067) for Python docstrings. + Please document your code when opening pull requests. + All methods/functions/modules *must* include docstrings that explain the parameters. + +3. We use [ruff](https://pypi.org/project/ruff/) to format and lint our code. (See the section on pre-commit below.) + +4. Please use [type hints](https://docs.python.org/3/library/typing.html) wherever applicable. + You can set up type checkers such as [mypy](https://mypy.readthedocs.io/) to use type hints in your development environment/IDE. + + + pip install mypy + + +### Pre-commit + +A number of [pre-commit](https://pre-commit.com/) hooks are used to improve code-quality. +Please run the following code to set up the pre-commit hooks: + + $ pre-commit install + +The hooks will be run at each `git commit`. +Please see `.pre-commit-config.yaml` for information on what hooks we run. + + +### Commit messages + +Writing good commit messages makes things easy to follow. +Please see these posts: + +- [How to write a Git commit message](https://cbea.ms/git-commit/) +- While not compulsory, we prefer [conventional commits](https://www.conventionalcommits.org/en/v1.0.0/) + + +## Tests + +Bug fixes and new features should include unit tests to test for correctness. +One can base new tests off the current ones included in the `tests/` directory. +To see how tests are run, please see the [GitHub Actions configuration file](https://github.com/NeuralEnsemble/libNeuroML/blob/development/.github/workflows/ci.yml). + +We use [pytest](https://docs.pytest.org/) for unit testing. +One can run it from the root of the repository: + + pytest + + +To run specific tests, one can use the `-k` flag: + + + pytest -k "..." + + +## Pull Request Process + +1. Please contribute pull requests against the `development` branch. +2. Please ensure that the automated build for your pull request passes. +3. Please write good commit messages (see the section above). + +### Updating your pull request branch + +Over time, as pull requests are reviewed, the `development` branch continues to move on with other changes. +Sometimes, it can be useful/necessary to pull in these changes to the pull request branch, using the following steps. + +Add the upstream libNeuroML repository as a remote: + + + git remote add upstream https://github.com/NeuralEnsemble/libNeuroML.git + + +Update your local copy of the `development` branch, and the remote copy in your fork: + + + git checkout development + git pull upstream development + git push + + +Pull in changes from development to your branch: + + + git checkout + git merge development + + +If there are merge conflicts, you will need to [resolve these](https://git-scm.com/book/en/v2/Git-Branching-Basic-Branching-and-Merging#_basic_merge_conflicts), since merging the feature branch in the pull request will also result in these. +After any merge conflicts have been resolved (or if there aren't any), you can +push your branch to your fork to update the pull request: + + + git push