Skip to content

Commit

Permalink
Fix/str safe (#746)
Browse files Browse the repository at this point in the history
* Add safe flag to types.str to prevent default casting.
Fix #737 : tags casting for morphologies in case of list[str].

* Set default type to unsafe string as the default value assigned to string is self._config_key, which is an int.

* Revert default safe flag for str. Adapt tests

* add key type handler

* use the key type handler when `key=True`

* revert test

tests should test the desired UX, they should not be adapted merely to avoid errors. the tests form the de facto specification of desired behaviour

* forgot to undo test debugging

---------

Co-authored-by: Robin De Schepper <robin.deschepper93@gmail.com>
  • Loading branch information
drodarie and Helveg committed Aug 5, 2023
1 parent 33b5276 commit e9ec092
Show file tree
Hide file tree
Showing 5 changed files with 38 additions and 14 deletions.
18 changes: 10 additions & 8 deletions bsb/config/_attrs.py
Original file line number Diff line number Diff line change
Expand Up @@ -195,7 +195,7 @@ def attr(**kwargs):
"""
Create a configuration attribute.
Only works when used inside of a class decorated with the :func:`node
Only works when used inside a class decorated with the :func:`node
<.config.node>`, :func:`dynamic <.config.dynamic>`, :func:`root <.config.root>`
or :func:`pluggable <.config.pluggable>` decorators.
Expand Down Expand Up @@ -429,7 +429,7 @@ def __init__(
self.key = key
self.default = default
self.call_default = call_default
self.type = self._set_type(type)
self.type = self._set_type(type, key)
self.unset = unset
self.hint = hint

Expand Down Expand Up @@ -475,7 +475,7 @@ def __set__(self, instance, value):
if _is_booted(root):
_boot_nodes(value, root.scaffold)

def _set_type(self, type):
def _set_type(self, type, key):
self._config_type = type
# Determine type of the attribute
if not type and self.default is not None:
Expand All @@ -484,7 +484,9 @@ def _set_type(self, type):
else:
t = builtins.type(self.default)
else:
t = type or str
from . import types

t = type or (key and types.key()) or types.str()
# This call wraps the type handler so that it accepts all reserved keyword args
# like `_parent` and `_key`
t = _wrap_reserved(t)
Expand Down Expand Up @@ -667,8 +669,8 @@ def fill(self, value, _parent, _key=None):
)
return _cfglist

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

def tree(self, instance):
Expand Down Expand Up @@ -807,8 +809,8 @@ def fill(self, value, _parent, _key=None):
_cfgdict.update(value or builtins.dict())
return _cfgdict

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

def tree(self, instance):
Expand Down
28 changes: 25 additions & 3 deletions bsb/config/types.py
Original file line number Diff line number Diff line change
Expand Up @@ -276,23 +276,26 @@ def __inv__(self, value):
return value


def str(strip=False, lower=False, upper=False):
def str(strip=False, lower=False, upper=False, safe=True):
"""
Type validator. Attempts to cast the value to an str, optionally with some sanitation.
Type validator. Attempts to cast the value to a str, optionally with some sanitation.
:param strip: Trim whitespaces
:type strip: bool
:param lower: Convert value to lowercase
:type lower: bool
:param upper: Convert value to uppercase
:type upper: bool
:param safe: If False, checks that the type of value is string before cast.
:type safe: bool
:returns: Type validator function
:raises: TypeError when value can't be cast.
:rtype: Callable
"""
handler_name = "str"
# Compile a custom function to sanitize the string according to args
fstr = "def f(s): return str(s)"
safety_check = "\n if not isinstance(s, str):\n raise TypeError()\n" if safe else ""
fstr = f"def f(s):{safety_check} return str(s)"
for add, mod in zip((strip, lower, upper), ("strip", "lower", "upper")):
if add:
fstr += f".{mod}()"
Expand Down Expand Up @@ -419,6 +422,25 @@ def type_handler(value):
return type_handler


def key():
"""
Type handler for keys in configuration trees. Keys can be either int indices of a
config list, or string keys of a config dict.
:returns: Type validator function
:rtype: Callable
"""

def type_handler(value):
if not (isinstance(value, builtins.int) or isinstance(value, builtins.str)):
raise TypeError(f"{type(value)} is not an int or str")
else:
return value

type_handler.__name__ = "configuration key"
return type_handler


def scalar_expand(scalar_type, size=None, expand=None):
"""
Create a method that expands a scalar into an array with a specific size or uses
Expand Down
2 changes: 1 addition & 1 deletion bsb/storage/_files.py
Original file line number Diff line number Diff line change
Expand Up @@ -474,7 +474,7 @@ class MorphologyDependencyNode(FilePipelineMixin, FileDependencyNode):
Name associated to the morphology. If not provided, the program will use the name of the file
in which the morphology is stored.
"""
tags = config.attr(type=types.dict(type=types.or_(str, types.list(str))))
tags = config.attr(type=types.dict(type=types.or_(types.str(), types.list(str))))
"""
Dictionary mapping SWC tags to sets of morphology labels.
"""
Expand Down
2 changes: 1 addition & 1 deletion tests/test_configuration.py
Original file line number Diff line number Diff line change
Expand Up @@ -1143,7 +1143,7 @@ def test_autocorrect(self):
class Test:
a = config.attr(default=5)
b = config.attr(type=float)
c = config.attr()
c = config.attr(type=types.str(safe=False))

cfg = Test({"a": "5", "b": "5.", "c": 3})
test_tree = cfg.__tree__()
Expand Down
2 changes: 1 addition & 1 deletion tests/test_issues.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ class Example:

@config.node
class Extension:
ex_mut = config.attr(required=True)
ex_mut = config.attr(type=int, required=True)
ref = config.ref(BothReference(), required=True)


Expand Down

0 comments on commit e9ec092

Please sign in to comment.