Skip to content

Commit

Permalink
Merge branch 'main' into New_docs
Browse files Browse the repository at this point in the history
  • Loading branch information
filimarc authored Jun 26, 2024
2 parents 8921ac5 + 22a4b04 commit d70c538
Show file tree
Hide file tree
Showing 48 changed files with 3,925 additions and 83 deletions.
4 changes: 4 additions & 0 deletions .github/devops/generate_public_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,10 @@ def get_public_api_map():
if isinstance(el, ast.Constant)
]
for api in module_api:
if api in public_api_map:
raise RuntimeError(
f"Duplicate api key: bsb.{module}.{api} and bsb.{public_api_map[api]}.{api}"
)
public_api_map[api] = module

return public_api_map
Expand Down
8 changes: 7 additions & 1 deletion .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,10 @@ repos:
rev: 5.12.0
hooks:
- id: isort
name: isort (python)
name: isort (python)
- repo: local
hooks:
- id: api-test
name: api-test
entry: python3 .github/devops/generate_public_api.py
language: system
16 changes: 16 additions & 0 deletions CHANGELOG
Original file line number Diff line number Diff line change
@@ -1,3 +1,19 @@
# 4.1.1
* Fix reference of file_ref during configuration parsing when importing nodes.
* Use a more strict rule for Jobs enqueuing.
* Use certifi to fix ssl certificate issues.

# 4.1.0
* Added `ParsesReferences` mixin from bsb-json to allow reference and import in configuration files.
This includes also a recursive parsing of the configuration files.
* Added `swap_axes` function in morphologies
* Added API test in pre-commit config and fix duplicate entries.
* Fix `PackageRequirement`, `ConfigurationListAttribute`, and `ConfigurationDictAttribute`
inverse functions
* Refactor `CodeDependencyNode` module attribute to be either a module like string or a path string
* Fix of assert_same_len
* Fix of test_particle_vd

# 4.0.0 - Too much to list

## 40.0.0a32
Expand Down
8 changes: 4 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
[![Code style: black](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/psf/black)
[![Documentation Status](https://readthedocs.org/projects/bsb/badge/?version=latest)](https://bsb.readthedocs.io/en/latest/?badge=latest)
[![Build Status](https://travis-ci.com/dbbs-lab/bsb.svg?branch=master)](https://travis-ci.com/dbbs-lab/bsb)
[![codecov](https://codecov.io/gh/dbbs-lab/bsb/branch/master/graph/badge.svg)](https://codecov.io/gh/dbbs-lab/bsb)
[![Build Status](https://travis-ci.com/dbbs-lab/bsb-core.svg?branch=main)](https://travis-ci.com/dbbs-lab/bsb-core)
[![codecov](https://codecov.io/gh/dbbs-lab/bsb-core/branch/main/graph/badge.svg)](https://codecov.io/gh/dbbs-lab/bsb-core)

<h3>:closed_book: Read the documentation on https://bsb.readthedocs.io/en/latest</h3>

Expand All @@ -25,14 +25,14 @@ Any package in the BSB ecosystem can be installed from PyPI through `pip`. Most
will want to install the main [bsb](https://pypi.org/project/bsb/) framework:

```
pip install "bsb~=4.0"
pip install "bsb~=4.1"
```

Advanced users looking to control install an unconventional combination of plugins might
be better of installing just this package, and the desired plugins:

```
pip install "bsb-core~=4.0"
pip install "bsb-core~=4.1"
```

Note that installing `bsb-core` does not come with any plugins installed and the usually
Expand Down
7 changes: 5 additions & 2 deletions bsb/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
install the `bsb` package instead.
"""

__version__ = "4.0.1"
__version__ = "4.1.1"

import functools
import importlib
Expand Down Expand Up @@ -142,7 +142,7 @@ def __dir__():
BaseCommand: typing.Type["bsb.cli.commands.BaseCommand"]
BidirectionalContact: typing.Type["bsb.postprocessing.BidirectionalContact"]
BootError: typing.Type["bsb.exceptions.BootError"]
BoxTree: typing.Type["bsb.voxels.BoxTree"]
BoxTree: typing.Type["bsb.trees.BoxTree"]
BoxTreeInterface: typing.Type["bsb.trees.BoxTreeInterface"]
Branch: typing.Type["bsb.morphologies.Branch"]
BranchLocTargetting: typing.Type["bsb.simulation.targetting.BranchLocTargetting"]
Expand Down Expand Up @@ -216,6 +216,8 @@ def __dir__():
ExternalSourceError: typing.Type["bsb.exceptions.ExternalSourceError"]
FileDependency: typing.Type["bsb.storage._files.FileDependency"]
FileDependencyNode: typing.Type["bsb.storage._files.FileDependencyNode"]
FileImportError: typing.Type["bsb.exceptions.FileImportError"]
FileReferenceError: typing.Type["bsb.exceptions.FileReferenceError"]
FileScheme: typing.Type["bsb.storage._files.FileScheme"]
FileStore: typing.Type["bsb.storage.interfaces.FileStore"]
FixedIndegree: typing.Type["bsb.connectivity.general.FixedIndegree"]
Expand Down Expand Up @@ -292,6 +294,7 @@ def __dir__():
ParameterError: typing.Type["bsb.exceptions.ParameterError"]
ParameterValue: typing.Type["bsb.simulation.parameter.ParameterValue"]
ParserError: typing.Type["bsb.exceptions.ParserError"]
ParsesReferences: typing.Type["bsb.config.parsers.ParsesReferences"]
Partition: typing.Type["bsb.topology.partition.Partition"]
PlacementError: typing.Type["bsb.exceptions.PlacementError"]
PlacementIndications: typing.Type["bsb.cell_types.PlacementIndications"]
Expand Down
2 changes: 1 addition & 1 deletion bsb/_util.py
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ def assert_samelen(*args):
"""
len_ = None
assert all(
(len_ := len(arg) if len_ is None else len(arg)) == len_ for arg in args
((len_ := len(arg)) if len_ is None else len(arg)) == len_ for arg in args
), "Input arguments should be of same length."


Expand Down
23 changes: 20 additions & 3 deletions bsb/config/_attrs.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
"""

import builtins
from functools import wraps

import errr

Expand Down Expand Up @@ -476,7 +477,7 @@ def __set__(self, instance, value):
self.attr_name,
) from e
self.flag_dirty(instance)
# The value was cast to its intented type and the new value can be set.
# The value was cast to its intended type and the new value can be set.
self.fset(instance, value)
root = _strict_root(instance)
if _is_booted(root):
Expand Down Expand Up @@ -687,7 +688,15 @@ def fill(self, value, _parent, _key=None):

def _set_type(self, type, key=None):
self.child_type = super()._set_type(type, key=False)
return self.fill

@wraps(self.fill)
def wrapper(*args, **kwargs):
return self.fill(*args, **kwargs)

# Expose children __inv__ function if it exists
if hasattr(self.child_type, "__inv__"):
setattr(wrapper, "__inv__", self.child_type.__inv__)
return wrapper

def tree(self, instance):
val = _getattr(instance, self.attr_name)
Expand Down Expand Up @@ -838,7 +847,15 @@ def fill(self, value, _parent, _key=None):

def _set_type(self, type, key=None):
self.child_type = super()._set_type(type, key=False)
return self.fill

@wraps(self.fill)
def wrapper(*args, **kwargs):
return self.fill(*args, **kwargs)

# Expose children __inv__ function if it exists
if hasattr(self.child_type, "__inv__"):
setattr(wrapper, "__inv__", self.child_type.__inv__)
return wrapper

def tree(self, instance):
val = _getattr(instance, self.attr_name).items()
Expand Down
6 changes: 3 additions & 3 deletions bsb/config/_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
from ..cell_types import CellType
from ..connectivity import ConnectionStrategy
from ..placement import PlacementStrategy
from ..postprocessing import AfterPlacementHook
from ..postprocessing import AfterConnectivityHook, AfterPlacementHook
from ..simulation.simulation import Simulation
from ..storage._files import (
CodeDependencyNode,
Expand Down Expand Up @@ -132,8 +132,8 @@ class Configuration:
"""
Network connectivity strategies
"""
after_connectivity: cfgdict[str, AfterPlacementHook] = config.dict(
type=AfterPlacementHook,
after_connectivity: cfgdict[str, AfterConnectivityHook] = config.dict(
type=AfterConnectivityHook,
)
simulations: cfgdict[str, Simulation] = config.dict(
type=Simulation,
Expand Down
174 changes: 174 additions & 0 deletions bsb/config/_parse_types.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,174 @@
from __future__ import annotations

from .. import warn
from ..exceptions import ConfigurationWarning, FileImportError


class parsed_node:
_key = None
"""Key to reference the node"""
_parent: parsed_node = None
"""Parent node"""

def location(self):
return "/" + "/".join(str(part) for part in self._location_parts([]))

def _location_parts(self, carry):
parent = self
while (parent := parent._parent) is not None:
if parent._parent is not None:
carry.insert(0, parent._key)
carry.append(self._key or "")
return carry

def __str__(self):
return f"<parsed file config '{super().__str__()}' at '{self.location()}'>"

def __repr__(self):
return super().__str__()


def _traverse_wrap(node, iter):
for key, value in iter:
if type(value) in recurse_handlers:
value, iter = recurse_handlers[type(value)](value, node)
value._key = key
value._parent = node
node[key] = value
_traverse_wrap(value, iter)


class parsed_dict(dict, parsed_node):
def merge(self, other):
"""
Recursively merge the values of another dictionary into us
"""
for key, value in other.items():
if key in self and isinstance(self[key], dict) and isinstance(value, dict):
if not isinstance(self[key], parsed_dict): # pragma: nocover
self[key] = d = parsed_dict(self[key])
d._key = key
d._parent = self
self[key].merge(value)
elif isinstance(value, dict):
self[key] = d = parsed_dict(value)
d._key = key
d._parent = self
_traverse_wrap(d, d.items())
else:
if isinstance(value, list):
value = parsed_list(value)
value._key = key
value._parent = self
self[key] = value

def rev_merge(self, other):
"""
Recursively merge ourselves onto another dictionary
"""
m = parsed_dict(other)
_traverse_wrap(m, m.items())
m.merge(self)
self.clear()
self.update(m)
for v in self.values():
if hasattr(v, "_parent"):
v._parent = self


class parsed_list(list, parsed_node):
pass


def _prep_dict(node, parent):
return parsed_dict(node), node.items()


def _prep_list(node, parent):
return parsed_list(node), enumerate(node)


recurse_handlers = {
dict: _prep_dict,
parsed_dict: _prep_dict,
list: _prep_list,
parsed_list: _prep_list,
}


class file_ref:
def __init__(self, node, doc, ref):
self.node = node
self.doc = doc
self.ref = ref
self.key_path = node.location()

def resolve(self, parser, target):
del self.node["$ref"]
self.node.rev_merge(target)

def __str__(self):
return "<file ref '{}'>".format(((self.doc + "#") if self.doc else "") + self.ref)


class file_imp(file_ref):
def __init__(self, node, doc, ref, values):
super().__init__(node, doc, ref)
self.values = values

def resolve(self, parser, target):
del self.node["$import"]
for key in self.values:
if key not in target:
raise FileImportError(
"'{}' does not exist in import node '{}'".format(key, self.ref)
)
if isinstance(target[key], dict):
imported = parsed_dict()
imported.merge(target[key])
imported._key = key
imported._parent = self.node
if key in self.node:
if isinstance(self.node[key], dict):
imported.merge(self.node[key])
else:
warn(
f"Importkey '{key}' of {self} is ignored because the parent"
f" already contains a key '{key}'"
f" with value '{self.node[key]}'.",
ConfigurationWarning,
stacklevel=3,
)
continue
self.node[key] = imported
self._fix_references(self.node[key], parser)
elif isinstance(target[key], list):
imported, iter = _prep_list(target[key], self.node)
imported._key = key
imported._parent = self.node
self.node[key] = imported
self._fix_references(self.node[key], parser)
_traverse_wrap(imported, iter)
else:
self.node[key] = target[key]

def _fix_references(self, node, parser):
# fix parser's references after the import.
if hasattr(parser, "references"):
for ref in parser.references:
node_loc = node.location()
if node_loc in ref.key_path:
# need to update the reference
# we descend the tree from the node until we reach the ref
# It should be here because of the merge.
loc_node = node
while node_loc != ref.key_path:
key = ref.key_path.split(node_loc, 1)[-1].split("/", 1)[-1]
if key not in loc_node: # pragma: nocover
raise ParserError(
f"Reference {ref.key_path} not found in {node_loc}. "
f"Should have been merged."
)
loc_node = node[key]
node_loc += "/" + key
ref.node = loc_node
Loading

0 comments on commit d70c538

Please sign in to comment.