diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 2054aaf2..d45934f3 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -157,16 +157,3 @@ jobs: - name: Publish package distributions to PyPI uses: pypa/gh-action-pypi-publish@release/v1 - - deploy_ebrains: - runs-on: ubuntu-latest - needs: release - environment: EBRAINS - steps: - - name: Mirror to EBRAINS Gitlab - uses: wei/git-sync@v3 - with: - source_repo: dbbs-lab/bsb-core - source_branch: main - destination_repo: https://push:${{ secrets.EBRAINS_GITLAB_ACCESS_TOKEN }}@gitlab.ebrains.eu/robinde/bsb-core.git - destination_branch: main diff --git a/CHANGELOG.md b/CHANGELOG.md index 8be08351..fa3760dc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,11 @@ +## [v4.5.4] - 2024-12-16 +### :bug: Bug Fixes +- [`516dc63`](https://github.com/dbbs-lab/bsb-core/commit/516dc631afcb77dbbc3c90c770af20b2f129e6d9) - Update configuration to store whenever an attribute is set *(PR [#900](https://github.com/dbbs-lab/bsb-core/pull/900) by [@filimarc](https://github.com/filimarc))* + - :arrow_lower_right: *fixes issue [#899](https://github.com/dbbs-lab/bsb-core/issues/899) opened by [@drodarie](https://github.com/drodarie)* +- [`5799cf8`](https://github.com/dbbs-lab/bsb-core/commit/5799cf848944207cbf21d7183b75c78f678bbb38) - postprocessing *(PR [#901](https://github.com/dbbs-lab/bsb-core/pull/901) by [@drodarie](https://github.com/drodarie))* + - :arrow_lower_right: *fixes issue [#887](https://github.com/dbbs-lab/bsb-core/issues/887) opened by [@drodarie](https://github.com/drodarie)* + + ## [v4.5.3] - 2024-10-29 ### :bug: Bug Fixes - [`0dac469`](https://github.com/dbbs-lab/bsb-core/commit/0dac46937c26a078b8b92864321919d8ed2ccac7) - mpi comm *(PR [#896](https://github.com/dbbs-lab/bsb-core/pull/896) by [@drodarie](https://github.com/drodarie))* @@ -480,3 +488,4 @@ MorphologyRepositories, morphologies, voxelization and touch detection. [v4.5.1]: https://github.com/dbbs-lab/bsb-core/compare/v4.5.0...v4.5.1 [v4.5.2]: https://github.com/dbbs-lab/bsb-core/compare/v4.5.1...v4.5.2 [v4.5.3]: https://github.com/dbbs-lab/bsb-core/compare/v4.5.2...v4.5.3 +[v4.5.4]: https://github.com/dbbs-lab/bsb-core/compare/v4.5.3...v4.5.4 diff --git a/bsb/__init__.py b/bsb/__init__.py index 7d9cce16..6066ba22 100644 --- a/bsb/__init__.py +++ b/bsb/__init__.py @@ -7,7 +7,7 @@ install the `bsb` package instead. """ -__version__ = "4.5.3" +__version__ = "4.5.4" import functools import importlib diff --git a/bsb/config/_attrs.py b/bsb/config/_attrs.py index b86e2f0d..efe85d9e 100644 --- a/bsb/config/_attrs.py +++ b/bsb/config/_attrs.py @@ -664,6 +664,7 @@ def __init__(self, *args, size=None, **kwargs): def __set__(self, instance, value, _key=None): _setattr(instance, self.attr_name, self.fill(value, _parent=instance)) + self.flag_dirty(instance) def __populate__(self, instance, value, unique_list=False): cfglist = _getattr(instance, self.attr_name) @@ -755,6 +756,7 @@ def add(self, key, *args, **kwargs): f"{self.get_node_name()} already contains '{key}'." + " Use `node[key] = value` if you want to overwrite it." ) + self._config_attr.flag_dirty(self._config_parent) self[key] = value = self._elem_type(*args, _parent=self, _key=key, **kwargs) return value diff --git a/bsb/postprocessing.py b/bsb/postprocessing.py index 60b90519..4249f694 100644 --- a/bsb/postprocessing.py +++ b/bsb/postprocessing.py @@ -13,10 +13,10 @@ class AfterPlacementHook(abc.ABC): name: str = config.attr(key=True) def queue(self, pool): - pool.queue( - lambda scaffold: scaffold.after_placement[self.name].postprocess(), - submitter=self, - ) + def static_function(scaffold, name): + return scaffold.after_placement[name].postprocess() + + pool.queue(static_function, (self.name,), submitter=self) @abc.abstractmethod def postprocess(self): @@ -28,10 +28,10 @@ class AfterConnectivityHook(abc.ABC): name: str = config.attr(key=True) def queue(self, pool): - pool.queue( - lambda scaffold: scaffold.after_connectivity[self.name].postprocess(), - submitter=self, - ) + def static_function(scaffold, name): + return scaffold.after_connectivity[name].postprocess() + + pool.queue(static_function, (self.name,), submitter=self) @abc.abstractmethod def postprocess(self): diff --git a/pyproject.toml b/pyproject.toml index ee68c32c..911c32db 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -100,7 +100,7 @@ line-length = 90 profile = "black" [tool.bumpversion] -current_version = "4.5.3" +current_version = "4.5.4" parse = "(?P\\d+)\\.(?P\\d+)\\.(?P\\d+)" serialize = ["{major}.{minor}.{patch}"] search = "{current_version}" diff --git a/tests/test_configuration.py b/tests/test_configuration.py index d890678a..f9edd626 100644 --- a/tests/test_configuration.py +++ b/tests/test_configuration.py @@ -1609,6 +1609,29 @@ def test_booted_root(self): Scaffold(cfg, self.storage) self.assertIsNotNone(_attrs._booted_root(cfg), "now it should be booted") + def test_updates(self): + """Test if tree is updated correctly""" + cfg = Configuration.default() + cfg.morphologies = ["dummy_neuron.swc"] + cfg.partitions.add("base_layer", thickness=100) + cfg.partitions.add("top_layer", thickness=100) + cfg.regions.add( + "brain_region", + type="stack", + children=[ + "base_layer", + "top_layer", + ], + ) + cfg_dict = cfg.__tree__() + self.assertEqual(cfg_dict["morphologies"], ["dummy_neuron.swc"]) + self.assertIn("brain_region", cfg_dict["regions"]) + cfg.morphologies.pop(0) + cfg.regions.pop("brain_region") + cfg_dict = cfg.__tree__() + self.assertEqual(cfg_dict["morphologies"], []) + self.assertEqual(cfg_dict["regions"], {}) + class TestNodeClass(unittest.TestCase): def test_standalone_node_name(self): diff --git a/tests/test_postprocessing.py b/tests/test_postprocessing.py new file mode 100644 index 00000000..c3504dc6 --- /dev/null +++ b/tests/test_postprocessing.py @@ -0,0 +1,87 @@ +import os +import unittest + +from bsb_test import RandomStorageFixture + +from bsb import ( + MPI, + AfterConnectivityHook, + AfterPlacementHook, + Configuration, + Scaffold, + config, +) + + +class TestAfterConnectivityHook( + RandomStorageFixture, unittest.TestCase, engine_name="hdf5" +): + def setUp(self): + super().setUp() + + @config.node + class TestAfterConn(AfterConnectivityHook): + def postprocess(self): + with open(f"test_after_conn_{MPI.get_rank()}.txt", "a") as f: + # make sure we have access to the scaffold context + f.write(f"{self.scaffold.configuration.name}\n") + + self.network = Scaffold( + config=Configuration.default( + name="Test config", + after_connectivity={"test_after_conn": TestAfterConn()}, + ), + storage=self.storage, + ) + + def test_after_connectivity_job(self): + self.network.compile() + if MPI.get_rank() == 0: + count_files = 0 + for filename in os.listdir(): + if filename.startswith("test_after_conn_"): + count_files += 1 + with open(filename, "r") as f: + lines = f.readlines() + self.assertEqual( + len(lines), 1, "The postprocess should be called only once." + ) + self.assertEqual(lines[0], "Test config\n") + os.remove(filename) + self.assertEqual(count_files, 1) + + +class TestAfterPlacementHook(RandomStorageFixture, unittest.TestCase, engine_name="hdf5"): + def setUp(self): + super().setUp() + + @config.node + class TestAfterPlace(AfterPlacementHook): + def postprocess(self): + with open(f"test_after_place_{MPI.get_rank()}.txt", "a") as f: + # make sure we have access to the scaffold context + f.write(f"{self.scaffold.configuration.name}\n") + + self.network = Scaffold( + config=Configuration.default( + name="Test config", + after_placement={"test_after_placement": TestAfterPlace()}, + ), + storage=self.storage, + ) + + def test_after_placement_job(self): + self.network.compile() + if MPI.get_rank() == 0: + count_files = 0 + for filename in os.listdir(): + if filename.startswith("test_after_place_"): + count_files += 1 + with open(filename, "r") as f: + lines = f.readlines() + self.assertEqual( + len(lines), 1, "The postprocess should be called only once." + ) + self.assertEqual(lines[0], "Test config\n") + os.remove(filename) + self.assertEqual(count_files, 1)