Repository: omni-us/jsonargparse

Issue:
Support for conversion to `torch.Tensor`
## 🐛 Bug report

I am encountering a problem using the package with Lightning.

When trying to specify an `init_args` which expects a `torch.Tensor` (for example when specifying the weights of a loss function), an error occurs because torch module expects a tensor, not a list.

It should be possible to [register](https://jsonargparse.readthedocs.io/en/stable/index.html#registering-types) pytorch tensors using

```python
def serializer(x):
    return x.tolist()

def deserializer(x):
    return torch.tensor(x)

register_type(torch.Tensor, serializer, deserializer)
```

However, doing so results in an error:

```bash
main: error: Parser key "model": Problem with given class_path "MyModel":
  - Configuration check failed :: Parser key "my_model": Value "Namespace(class_path='MySubModel', init_args=Namespace(..., loss=Namespace(class_path='torch.nn.CrossEntropyLoss', init_args=Namespace(weight=tensor([  7.6200, 104.9100,   1.5600,  20.6500,  14.0500,  14.0600,   1.0000,  36.0700]), size_average=None, ignore_index=-100, reduce=None, reduction='mean', label_smoothing=0.0))))" does not validate against any of the types in typing.Optional[MySubModel]:
    - Boolean value of Tensor with more than one value is ambiguous
...
```

Since a list is specified in the yaml and the namespace shows a `tensor([...])`, it looks like the deserializer is working correctly, but something is going wrong.

### Expected behavior

The list should be converted to a tensor without error.

### Environment

- jsonargparse 4.15.2
- Python 3.10.6



Discussion:
sonarcloud[bot]: Kudos, SonarCloud Quality Gate passed!&nbsp; &nbsp; [![Quality Gate passed](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/QualityGateBadge/passed-16px.png 'Quality Gate passed')](https://sonarcloud.io/dashboard?id=omni-us_jsonargparse&pullRequest=197)

[![Bug](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/bug-16px.png 'Bug')](https://sonarcloud.io/project/issues?id=omni-us_jsonargparse&pullRequest=197&resolved=false&types=BUG) [![A](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/RatingBadge/A-16px.png 'A')](https://sonarcloud.io/project/issues?id=omni-us_jsonargparse&pullRequest=197&resolved=false&types=BUG) [0 Bugs](https://sonarcloud.io/project/issues?id=omni-us_jsonargparse&pullRequest=197&resolved=false&types=BUG)  
[![Vulnerability](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/vulnerability-16px.png 'Vulnerability')](https://sonarcloud.io/project/issues?id=omni-us_jsonargparse&pullRequest=197&resolved=false&types=VULNERABILITY) [![A](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/RatingBadge/A-16px.png 'A')](https://sonarcloud.io/project/issues?id=omni-us_jsonargparse&pullRequest=197&resolved=false&types=VULNERABILITY) [0 Vulnerabilities](https://sonarcloud.io/project/issues?id=omni-us_jsonargparse&pullRequest=197&resolved=false&types=VULNERABILITY)  
[![Security Hotspot](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/security_hotspot-16px.png 'Security Hotspot')](https://sonarcloud.io/project/security_hotspots?id=omni-us_jsonargparse&pullRequest=197&resolved=false&types=SECURITY_HOTSPOT) [![A](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/RatingBadge/A-16px.png 'A')](https://sonarcloud.io/project/security_hotspots?id=omni-us_jsonargparse&pullRequest=197&resolved=false&types=SECURITY_HOTSPOT) [0 Security Hotspots](https://sonarcloud.io/project/security_hotspots?id=omni-us_jsonargparse&pullRequest=197&resolved=false&types=SECURITY_HOTSPOT)  
[![Code Smell](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/code_smell-16px.png 'Code Smell')](https://sonarcloud.io/project/issues?id=omni-us_jsonargparse&pullRequest=197&resolved=false&types=CODE_SMELL) [![A](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/RatingBadge/A-16px.png 'A')](https://sonarcloud.io/project/issues?id=omni-us_jsonargparse&pullRequest=197&resolved=false&types=CODE_SMELL) [0 Code Smells](https://sonarcloud.io/project/issues?id=omni-us_jsonargparse&pullRequest=197&resolved=false&types=CODE_SMELL)

[![No Coverage information](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/CoverageChart/NoCoverageInfo-16px.png 'No Coverage information')](https://sonarcloud.io/component_measures?id=omni-us_jsonargparse&pullRequest=197) No Coverage information  
[![0.0%](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/Duplications/3-16px.png '0.0%')](https://sonarcloud.io/component_measures?id=omni-us_jsonargparse&pullRequest=197&metric=new_duplicated_lines_density&view=list) [0.0% Duplication](https://sonarcloud.io/component_measures?id=omni-us_jsonargparse&pullRequest=197&metric=new_duplicated_lines_density&view=list)


codecov-commenter: # [Codecov](https://codecov.io/gh/omni-us/jsonargparse/pull/197?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=omni-us) Report
Base: **100.00**% // Head: **100.00**% // No change to project coverage :thumbsup:
> Coverage data is based on head [(`7a0f7df`)](https://codecov.io/gh/omni-us/jsonargparse/pull/197?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=omni-us) compared to base [(`fe55400`)](https://codecov.io/gh/omni-us/jsonargparse/commit/fe55400e9564dbaf64219603ea1ce9de13a64ee5?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=omni-us).
> Patch coverage: 100.00% of modified lines in pull request are covered.

<details><summary>Additional details and impacted files</summary>


```diff
@@            Coverage Diff            @@
##            master      #197   +/-   ##
=========================================
  Coverage   100.00%   100.00%           
=========================================
  Files           17        17           
  Lines         4497      4497           
=========================================
  Hits          4497      4497           
```

| Flag | Coverage Δ | |
|---|---|---|
| py3.10 | `91.34% <100.00%> (ø)` | |
| py3.10_all | `99.93% <100.00%> (ø)` | |
| py3.6 | `90.98% <100.00%> (ø)` | |
| py3.6_all | `99.64% <100.00%> (ø)` | |
| py3.7 | `91.07% <100.00%> (ø)` | |
| py3.7_all | `99.84% <100.00%> (ø)` | |
| py3.8 | `91.32% <100.00%> (ø)` | |
| py3.8_all | `99.91% <100.00%> (ø)` | |
| py3.9 | `91.34% <100.00%> (ø)` | |
| py3.9_all | `99.91% <100.00%> (ø)` | |

Flags with carried forward coverage won't be shown. [Click here](https://docs.codecov.io/docs/carryforward-flags?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=omni-us#carryforward-flags-in-the-pull-request-comment) to find out more.

| [Impacted Files](https://codecov.io/gh/omni-us/jsonargparse/pull/197?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=omni-us) | Coverage Δ | |
|---|---|---|
| [jsonargparse/typehints.py](https://codecov.io/gh/omni-us/jsonargparse/pull/197/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=omni-us#diff-anNvbmFyZ3BhcnNlL3R5cGVoaW50cy5weQ==) | `100.00% <100.00%> (ø)` | |

Help us with your feedback. Take ten seconds to tell us [how you rate us](https://about.codecov.io/nps?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=omni-us). Have a feature suggestion? [Share it here.](https://app.codecov.io/gh/feedback/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=omni-us)

</details>

[:umbrella: View full report at Codecov](https://codecov.io/gh/omni-us/jsonargparse/pull/197?src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=omni-us).   
:loudspeaker: Do you have feedback about the report comment? [Let us know in this issue](https://about.codecov.io/codecov-pr-comment-feedback/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=omni-us).


Patch:
diff --git a/CHANGELOG.rst b/CHANGELOG.rst
index 1d800e2..005aa34 100644
--- a/CHANGELOG.rst
+++ b/CHANGELOG.rst
@@ -21,6 +21,8 @@ Fixed
 - ``parse_string`` raises ``AttributeError`` when given a simple string.
 - Added missing ``return_parser`` deprecation warning when ``CLI`` has
   subcommands.
+- Parsing fails for registered types that can't be cast to boolean `#196
+  <https://github.com/omni-us/jsonargparse/issues/196>`__.
 
 Changed
 ^^^^^^^
diff --git a/jsonargparse/actions.py b/jsonargparse/actions.py
index c93b2df..e9ee140 100644
--- a/jsonargparse/actions.py
+++ b/jsonargparse/actions.py
@@ -480,7 +480,7 @@ class ActionParser:
         """Initializer for ActionParser instance.
 
         Args:
-            parser: A parser to parse the option with.
+            parser (Optional[ArgumentParser]): A parser to parse the option with.
 
         Raises:
             ValueError: If the parser parameter is invalid.
diff --git a/jsonargparse/core.py b/jsonargparse/core.py
index 57bf6d7..f700fdc 100644
--- a/jsonargparse/core.py
+++ b/jsonargparse/core.py
@@ -212,7 +212,7 @@ class ArgumentParser(ActionsContainer, ArgumentLinking, argparse.ArgumentParser)
             self.groups = {}
         self.required_args: Set[str] = set()
         self.save_path_content: Set[str] = set()
-        self.default_config_files = default_config_files
+        self.default_config_files = default_config_files  # type: ignore
         self.default_meta = default_meta
         self.default_env = default_env
         self.env_prefix = env_prefix
@@ -861,11 +861,11 @@ class ArgumentParser(ActionsContainer, ArgumentLinking, argparse.ArgumentParser)
 
     ## Methods related to defaults ##
 
-    def set_defaults(self, *args, **kwargs) -> None:
+    def set_defaults(self, *args: Dict[str, Any], **kwargs: Any) -> None:
         """Sets default values from dictionary or keyword arguments.
 
         Args:
-            *args (dict): Dictionary defining the default values to set.
+            *args: Dictionary defining the default values to set.
             **kwargs: Sets default values based on keyword arguments.
 
         Raises:
@@ -938,7 +938,6 @@ class ArgumentParser(ActionsContainer, ArgumentLinking, argparse.ArgumentParser)
         """Returns a namespace with all default values.
 
         Args:
-            nested: Whether the namespace should be nested.
             skip_check: Whether to skip check if configuration is valid.
 
         Returns:
@@ -1314,7 +1313,7 @@ class ArgumentParser(ActionsContainer, ArgumentLinking, argparse.ArgumentParser)
     ## Properties ##
 
     @property
-    def error_handler(self):
+    def error_handler(self) -> Optional[Callable[['ArgumentParser', str], None]]:
         """Property for the error_handler function that is called when there are parsing errors.
 
         :getter: Returns the current error_handler function.
@@ -1327,7 +1326,7 @@ class ArgumentParser(ActionsContainer, ArgumentLinking, argparse.ArgumentParser)
 
 
     @error_handler.setter
-    def error_handler(self, error_handler):
+    def error_handler(self, error_handler: Optional[Callable[['ArgumentParser', str], None]]):
         if callable(error_handler) or error_handler is None:
             self._error_handler = error_handler
         else:
@@ -1335,7 +1334,7 @@ class ArgumentParser(ActionsContainer, ArgumentLinking, argparse.ArgumentParser)
 
 
     @property
-    def default_config_files(self):
+    def default_config_files(self) -> List[str]:
         """Default config file locations.
 
         :getter: Returns the current default config file locations.
@@ -1348,7 +1347,7 @@ class ArgumentParser(ActionsContainer, ArgumentLinking, argparse.ArgumentParser)
 
 
     @default_config_files.setter
-    def default_config_files(self, default_config_files:Optional[List[str]]):
+    def default_config_files(self, default_config_files: Optional[List[str]]):
         if default_config_files is None:
             self._default_config_files = []
         elif isinstance(default_config_files, list) and all(isinstance(x, str) for x in default_config_files):
diff --git a/jsonargparse/typehints.py b/jsonargparse/typehints.py
index 5c7a680..f3344ce 100644
--- a/jsonargparse/typehints.py
+++ b/jsonargparse/typehints.py
@@ -384,7 +384,7 @@ class ActionTypeHint(Action):
                 path_meta = val.pop('__path__', None) if isinstance(val, dict) else None
 
                 prev_val = cfg.get(self.dest) if cfg else None
-                if not prev_val and not sub_defaults.get() and is_subclass_spec(self.default):
+                if prev_val is None and not sub_defaults.get() and is_subclass_spec(self.default):
                     prev_val = Namespace(class_path=self.default.class_path)
 
                 kwargs = {
diff --git a/jsonargparse/util.py b/jsonargparse/util.py
index e71042e..0f86ba8 100644
--- a/jsonargparse/util.py
+++ b/jsonargparse/util.py
@@ -586,12 +586,12 @@ class LoggerProperty:
 
     def __init__(self, *args, logger: Union[bool, str, dict, logging.Logger] = False, **kwargs):
         """Initializer for LoggerProperty class."""
-        self.logger = logger
+        self.logger = logger  # type: ignore
         super().__init__(*args, **kwargs)
 
 
     @property
-    def logger(self):
+    def logger(self) -> logging.Logger:
         """The logger property for the class.
 
         :getter: Returns the current logger.


Test patch:
diff --git a/jsonargparse_tests/test_typing.py b/jsonargparse_tests/test_typing.py
index bbaf0dd..a800071 100755
--- a/jsonargparse_tests/test_typing.py
+++ b/jsonargparse_tests/test_typing.py
@@ -17,6 +17,7 @@ from jsonargparse.typing import (
     path_type,
     PositiveFloat,
     PositiveInt,
+    register_type,
     RegisteredType,
     registered_types,
     restricted_number_type,
@@ -254,5 +255,26 @@ class OtherTests(unittest.TestCase):
                 self.assertRaises(ValueError, lambda: timedelta_type.deserializer(delta_in))
 
 
+    def test_register_non_bool_cast_type(self):
+        class Elems:
+            def __init__(self, *elems):
+                self.elems = list(elems)
+            def __bool__(self):
+                raise RuntimeError('bool not supported')
+
+        self.assertRaises(RuntimeError, lambda: not Elems(1, 2))
+        register_type(Elems, lambda x: x.elems, lambda x: Elems(*x))
+
+        parser = ArgumentParser(error_handler=None)
+        parser.add_argument('--elems', type=Elems)
+        cfg = parser.parse_args(['--elems=[1, 2, 3]'])
+        self.assertIsInstance(cfg.elems, Elems)
+        self.assertEqual(cfg.elems.elems, [1, 2, 3])
+        dump = parser.dump(cfg, format='json')
+        self.assertEqual(dump, '{"elems":[1,2,3]}')
+
+        del registered_types[Elems]
+
+
 if __name__ == '__main__':
     unittest.main(verbosity=2)