Skip to content

Commit

Permalink
Merge pull request #190 from NeuralEnsemble/feat/build-time-validation
Browse files Browse the repository at this point in the history
Enable toggling of build time validation
  • Loading branch information
sanjayankur31 authored May 30, 2024
2 parents 477fac7 + 16d2d91 commit a767b2b
Show file tree
Hide file tree
Showing 7 changed files with 262 additions and 10 deletions.
15 changes: 15 additions & 0 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
@@ -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
112 changes: 112 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
@@ -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 <feature branch being used for PR>
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
55 changes: 54 additions & 1 deletion neuroml/__init__.py
Original file line number Diff line number Diff line change
@@ -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):
Expand All @@ -10,3 +21,45 @@ 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
.. versionadded:: 0.6.0
"""
build_time_validation.ENABLED = True
logger.info("Build time validation has been enabled.")


def get_build_time_validation() -> bool:
"""Get build time validation
.. versionadded:: 0.6.0
:returns: state of build time validation
:rtype: bool
"""
return build_time_validation.ENABLED
27 changes: 27 additions & 0 deletions neuroml/build_time_validation.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
#!/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.
.. versionadded:: 0.6.0
File: build_time_validation.py
Copyright 2024 NeuroML contributors
"""

ENABLED = True
24 changes: 15 additions & 9 deletions neuroml/nml/generatedssupersuper.py
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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
Expand All @@ -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:
Expand All @@ -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
Expand Down Expand Up @@ -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):
Expand Down Expand Up @@ -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:
Expand Down
38 changes: 38 additions & 0 deletions neuroml/test/test_global_validation.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
#!/usr/bin/env python3
"""
Test global validation toggles
File: test_global_validation.py
Copyright 2024 Ankur Sinha
Author: Ankur Sinha <sanjay DOT ankur AT gmail DOT com>
"""

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")
1 change: 1 addition & 0 deletions setup.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ dev =
pytest
ruff
natsort
pre-commit

doc =
sphinxcontrib-bibtex
Expand Down

0 comments on commit a767b2b

Please sign in to comment.