Repository: hdmf-dev/hdmf

Issue:
Getting TypeError: incorrect type for 'bool' attribute in extension
I’m having a problem creating an [extension](https://github.com/ben-dichter-consulting/ndx-labmetadata-giocomo), more specifically, to create an attribute of type `bool`.

On the list of valid primary dtypes, I get:
{'isodatetime', 'datetime64', 'region', 'int8', 'int16', 'bool', 'int', 'ascii', 'int32', 'text', 'bytes', 'int64', 'utf-8', 'double', 'utf', 'datetime', 'uint32', 'uint', 'uint64', 'uint16', 'object', 'numeric', 'float32', 'float', 'short', 'float64', 'long', 'uint8', 'utf8'}

Then I try to add an attribute of type `bool`:
```python
    LabMetaData_ext.add_attribute(
        name='high_pass_filtered',
        doc='binary variable as to whether raw data was high-pass filtered or not',
        dtype='bool',
        shape=None,
    )
```
[Here’s](https://github.com/ben-dichter-consulting/ndx-labmetadata-giocomo/blob/master/src/spec/create_extension_spec.py) the full code.

When I run [this test](https://github.com/ben-dichter-consulting/ndx-labmetadata-giocomo/blob/master/src/pynwb/ndx_labmetadata_giocomo/test.py), it apparently works well in saving the nwb file with the new extension, I can even open the file with HDFView, but fails opening the new file with NWBHDF5IO. The error:

```
Traceback (most recent call last):
  File "C:\Users\Luiz\Anaconda3\envs\nwbn_conversion\lib\site-packages\hdmf\build\map.py", line 1207, in construct
    obj.__init__(**kwargs)
  File "C:\Users\Luiz\Anaconda3\envs\nwbn_conversion\lib\site-packages\hdmf\utils.py", line 436, in func_call
    raise_from(ExceptionType(msg), None)
  File "<string>", line 3, in raise_from
TypeError: incorrect type for 'high_pass_filtered' (got 'bool_', expected 'bool')

The above exception was the direct cause of the following exception:

Traceback (most recent call last):
  File "test.py", line 28, in <module>
    nwb = io.read()
  File "C:\Users\Luiz\Anaconda3\envs\nwbn_conversion\lib\site-packages\hdmf\backends\hdf5\h5tools.py", line 293, in read
    return call_docval_func(super(HDF5IO, self).read, kwargs)
  File "C:\Users\Luiz\Anaconda3\envs\nwbn_conversion\lib\site-packages\hdmf\utils.py", line 327, in call_docval_func
    return func(*fargs, **fkwargs)
  File "C:\Users\Luiz\Anaconda3\envs\nwbn_conversion\lib\site-packages\hdmf\utils.py", line 438, in func_call
    return func(self, **parsed['args'])
  File "C:\Users\Luiz\Anaconda3\envs\nwbn_conversion\lib\site-packages\hdmf\backends\io.py", line 35, in read
    container = self.__manager.construct(f_builder)
  File "C:\Users\Luiz\Anaconda3\envs\nwbn_conversion\lib\site-packages\hdmf\utils.py", line 438, in func_call
    return func(self, **parsed['args'])
  File "C:\Users\Luiz\Anaconda3\envs\nwbn_conversion\lib\site-packages\hdmf\build\map.py", line 208, in construct
    result = self.__type_map.construct(builder, self, None)
  File "C:\Users\Luiz\Anaconda3\envs\nwbn_conversion\lib\site-packages\hdmf\utils.py", line 438, in func_call
    return func(self, **parsed['args'])
  File "C:\Users\Luiz\Anaconda3\envs\nwbn_conversion\lib\site-packages\hdmf\build\map.py", line 1712, in construct
    return attr_map.construct(builder, build_manager, parent)
  File "C:\Users\Luiz\Anaconda3\envs\nwbn_conversion\lib\site-packages\hdmf\utils.py", line 438, in func_call
    return func(self, **parsed['args'])
  File "C:\Users\Luiz\Anaconda3\envs\nwbn_conversion\lib\site-packages\hdmf\build\map.py", line 1175, in construct
    subspecs = self.__get_subspec_values(builder, self.spec, manager)
  File "C:\Users\Luiz\Anaconda3\envs\nwbn_conversion\lib\site-packages\hdmf\build\map.py", line 1117, in __get_subspec_values
    self.__get_sub_builders(groups, spec.groups, manager, ret)
  File "C:\Users\Luiz\Anaconda3\envs\nwbn_conversion\lib\site-packages\hdmf\build\map.py", line 1155, in __get_sub_builders
    ret.update(self.__get_subspec_values(sub_builder, subspec, manager))
  File "C:\Users\Luiz\Anaconda3\envs\nwbn_conversion\lib\site-packages\hdmf\build\map.py", line 1117, in __get_subspec_values
    self.__get_sub_builders(groups, spec.groups, manager, ret)
  File "C:\Users\Luiz\Anaconda3\envs\nwbn_conversion\lib\site-packages\hdmf\build\map.py", line 1147, in __get_sub_builders
    sub_builder = self.__flatten(sub_builder, subspec, manager)
  File "C:\Users\Luiz\Anaconda3\envs\nwbn_conversion\lib\site-packages\hdmf\build\map.py", line 1160, in __flatten
    tmp = [manager.construct(b) for b in sub_builder]
  File "C:\Users\Luiz\Anaconda3\envs\nwbn_conversion\lib\site-packages\hdmf\build\map.py", line 1160, in <listcomp>
    tmp = [manager.construct(b) for b in sub_builder]
  File "C:\Users\Luiz\Anaconda3\envs\nwbn_conversion\lib\site-packages\hdmf\utils.py", line 438, in func_call
    return func(self, **parsed['args'])
  File "C:\Users\Luiz\Anaconda3\envs\nwbn_conversion\lib\site-packages\hdmf\build\map.py", line 204, in construct
    result = self.__type_map.construct(builder, self, parent)
  File "C:\Users\Luiz\Anaconda3\envs\nwbn_conversion\lib\site-packages\hdmf\utils.py", line 438, in func_call
    return func(self, **parsed['args'])
  File "C:\Users\Luiz\Anaconda3\envs\nwbn_conversion\lib\site-packages\hdmf\build\map.py", line 1712, in construct
    return attr_map.construct(builder, build_manager, parent)
  File "C:\Users\Luiz\Anaconda3\envs\nwbn_conversion\lib\site-packages\hdmf\utils.py", line 438, in func_call
    return func(self, **parsed['args'])
  File "C:\Users\Luiz\Anaconda3\envs\nwbn_conversion\lib\site-packages\hdmf\build\map.py", line 1210, in construct
    raise_from(Exception(msg), ex)
  File "<string>", line 3, in raise_from
Exception: Could not construct LabMetaData_ext object
```

I’m guessing the source is here?
```
TypeError: incorrect type for 'high_pass_filtered' (got 'bool_', expected 'bool')
```
Does anyone have a hint how to solve it?


##
    Python Version: 3.7
    Operating System: Windows
    HDMF Version: 1.3.3

## Checklist

- [x ] Have you ensured the feature or change was not already [reported](https://github.com/hdmf-dev/hdmf/issues) ?
- [ x] Have you included a brief and descriptive title?
- [ x] Have you included a clear description of the problem you are trying to solve?
- [ x] Have you included a minimal code snippet that reproduces the issue you are encountering?
- [ x] Have you checked our [Contributing](https://github.com/hdmf-dev/hdmf/blob/dev/docs/CONTRIBUTING.rst) document?


Discussion:
codecov[bot]: # [Codecov](https://codecov.io/gh/hdmf-dev/hdmf/pull/203?src=pr&el=h1) Report
> :exclamation: No coverage uploaded for pull request base (`dev@42026c5`). [Click here to learn what that means](https://docs.codecov.io/docs/error-reference#section-missing-base-commit).
> The diff coverage is `66.66%`.

[![Impacted file tree graph](https://codecov.io/gh/hdmf-dev/hdmf/pull/203/graphs/tree.svg?width=650&token=PvIvUDjgcP&height=150&src=pr)](https://codecov.io/gh/hdmf-dev/hdmf/pull/203?src=pr&el=tree)

```diff
@@          Coverage Diff           @@
##             dev     #203   +/-   ##
======================================
  Coverage       ?   70.33%           
======================================
  Files          ?       30           
  Lines          ?     5956           
  Branches       ?     1399           
======================================
  Hits           ?     4189           
  Misses         ?     1329           
  Partials       ?      438
```


| [Impacted Files](https://codecov.io/gh/hdmf-dev/hdmf/pull/203?src=pr&el=tree) | Coverage Δ | |
|---|---|---|
| [src/hdmf/utils.py](https://codecov.io/gh/hdmf-dev/hdmf/pull/203/diff?src=pr&el=tree#diff-c3JjL2hkbWYvdXRpbHMucHk=) | `89.7% <66.66%> (ø)` | |

------

[Continue to review full report at Codecov](https://codecov.io/gh/hdmf-dev/hdmf/pull/203?src=pr&el=continue).
> **Legend** - [Click here to learn more](https://docs.codecov.io/docs/codecov-delta)
> `Δ = absolute <relative> (impact)`, `ø = not affected`, `? = missing data`
> Powered by [Codecov](https://codecov.io/gh/hdmf-dev/hdmf/pull/203?src=pr&el=footer). Last update [42026c5...64e0c65](https://codecov.io/gh/hdmf-dev/hdmf/pull/203?src=pr&el=lastupdated). Read the [comment docs](https://docs.codecov.io/docs/pull-request-comments).


Patch:
diff --git a/src/hdmf/utils.py b/src/hdmf/utils.py
index 289c182..fd800c8 100644
--- a/src/hdmf/utils.py
+++ b/src/hdmf/utils.py
@@ -51,6 +51,8 @@ def __type_okay(value, argtype, allow_none=False):
             return __is_int(value)
         elif argtype == 'float':
             return __is_float(value)
+        elif argtype == 'bool':
+            return __is_bool(value)
         return argtype in [cls.__name__ for cls in value.__class__.__mro__]
     elif isinstance(argtype, type):
         if argtype == six.text_type:
@@ -61,6 +63,8 @@ def __type_okay(value, argtype, allow_none=False):
             return __is_int(value)
         elif argtype is float:
             return __is_float(value)
+        elif argtype is bool:
+            return __is_bool(value)
         return isinstance(value, argtype)
     elif isinstance(argtype, tuple) or isinstance(argtype, list):
         return any(__type_okay(value, i) for i in argtype)
@@ -100,6 +104,10 @@ def __is_float(value):
     return any(isinstance(value, i) for i in SUPPORTED_FLOAT_TYPES)
 
 
+def __is_bool(value):
+    return isinstance(value, bool) or isinstance(value, np.bool_)
+
+
 def __format_type(argtype):
     if isinstance(argtype, str):
         return argtype


Test patch:
diff --git a/tests/unit/build_tests/test_io_map.py b/tests/unit/build_tests/test_io_map.py
index ae839dd..1405364 100644
--- a/tests/unit/build_tests/test_io_map.py
+++ b/tests/unit/build_tests/test_io_map.py
@@ -701,6 +701,22 @@ class TestConvertDtype(unittest.TestCase):
         self.assertTupleEqual(ret, match)
         self.assertIs(ret[0].dtype.type, match[1])
 
+    def test_bool_spec(self):
+        spec_type = 'bool'
+        spec = DatasetSpec('an example dataset', spec_type, name='data')
+
+        value = np.bool_(True)
+        ret = ObjectMapper.convert_dtype(spec, value)
+        match = (value, np.bool_)
+        self.assertTupleEqual(ret, match)
+        self.assertIs(type(ret[0]), match[1])
+
+        value = True
+        ret = ObjectMapper.convert_dtype(spec, value)
+        match = (value, np.bool_)
+        self.assertTupleEqual(ret, match)
+        self.assertIs(type(ret[0]), match[1])
+
 
 if __name__ == '__main__':
     unittest.main()
diff --git a/tests/unit/utils_test/test_docval.py b/tests/unit/utils_test/test_docval.py
index ebe23d1..5b711de 100644
--- a/tests/unit/utils_test/test_docval.py
+++ b/tests/unit/utils_test/test_docval.py
@@ -1,5 +1,6 @@
 import unittest
 from six import text_type
+import numpy as np
 
 from hdmf.utils import docval, fmt_docval_args, get_docval, popargs
 
@@ -478,6 +479,19 @@ class TestDocValidator(unittest.TestCase):
         with self.assertRaisesRegex(ValueError, r'Function __init__ has no docval arguments'):
             get_docval(self.test_obj.__init__, 'arg3')
 
+    def test_bool_type(self):
+        @docval({'name': 'arg1', 'type': bool, 'doc': 'this is a bool'})
+        def method(self, **kwargs):
+            return popargs('arg1', kwargs)
+
+        res = method(self, arg1=True)
+        self.assertEqual(res, True)
+        self.assertIsInstance(res, bool)
+
+        res = method(self, arg1=np.bool_(True))
+        self.assertEqual(res, np.bool_(True))
+        self.assertIsInstance(res, np.bool_)
+
 
 class TestDocValidatorChain(unittest.TestCase):