Repository: omni-us/jsonargparse

Issue:
Cannot correctly parse some import paths when using config file for LightningCLI
<!-- If you like this project, please ⭐ star it https://github.com/omni-us/jsonargparse/ -->

## 🐛 Bug report

I would like to use CLI with config.yaml file to manage my project. But I found some classes in TorchAudio cannot be correctly parsed. The error involves importing a window function from Pytorch. For example, if the class doesn't have a window function as the argument explicitly like MFCC or LFCC, it can be parsed and imported successfully. Otherwise, it fails in parsing the class or just identifies the import path as a string (like MelSpectrogram).

### To reproduce

```python
from lightning.pytorch.cli import LightningCLI

cli = LightningCLI(run=True, save_config_kwargs={"overwrite": True})
```

```yaml
model:
  class_path: models.AudioSystem
  init_args:
    model:
      class_path: models.AudioModel
      init_args:
        feat_dim: 80 # n_feat
        trunc_len: 750
        num_classes: 1000
    feature_extractor:
      class_path: torchaudio.transforms.MelSpectrogram
      init_args:
        sample_rate: 16000
        n_mels: 80
        n_fft: 512
        win_length: 512
        hop_length: 160
        window_fn: torch.hann_window # It is a default argument. Whether configuring it explicitly or not, the error occurred.
    delta_order: 2
```

### Expected behavior

The `MelSpectrogram` class should be correctly imported and instantiated. But I got some errors as

```python
# Configure the window function explicitly
ValueError: Only possible to serialize an importable object, given <built-in method hann_window of type object at 0x7feed842f840>: module 'torch' has no attribute '_VariableFunctionsClass'

# Don't configure the window function in config file, using the default argument.
Traceback (most recent call last):
  File "/workspace/zengchang/work/antispoofing/train.py", line 128, in <module>
    main()
  File "/workspace/zengchang/work/antispoofing/train.py", line 125, in main
    cli_main()
  File "/workspace/zengchang/work/antispoofing/train.py", line 68, in cli_main
    cli = CustomCLI(
  File "/opt/conda/envs/asvspoof5/lib/python3.10/site-packages/lightning/pytorch/cli.py", line 385, in __init__
    self.instantiate_classes()
  File "/opt/conda/envs/asvspoof5/lib/python3.10/site-packages/lightning/pytorch/cli.py", line 535, in instantiate_classes
    self.config_init = self.parser.instantiate_classes(self.config)
  File "/opt/conda/envs/asvspoof5/lib/python3.10/site-packages/jsonargparse/_deprecated.py", line 141, in patched_instantiate_classes
    cfg = self._unpatched_instantiate_classes(cfg, **kwargs)
  File "/opt/conda/envs/asvspoof5/lib/python3.10/site-packages/jsonargparse/_core.py", line 1196, in instantiate_classes
    cfg[subcommand] = subparser.instantiate_classes(cfg[subcommand], instantiate_groups=instantiate_groups)
  File "/opt/conda/envs/asvspoof5/lib/python3.10/site-packages/jsonargparse/_deprecated.py", line 141, in patched_instantiate_classes
    cfg = self._unpatched_instantiate_classes(cfg, **kwargs)
  File "/opt/conda/envs/asvspoof5/lib/python3.10/site-packages/jsonargparse/_core.py", line 1187, in instantiate_classes
    parent[key] = component.instantiate_classes(value)
  File "/opt/conda/envs/asvspoof5/lib/python3.10/site-packages/jsonargparse/_typehints.py", line 565, in instantiate_classes
    value[num] = adapt_typehints(
  File "/opt/conda/envs/asvspoof5/lib/python3.10/site-packages/jsonargparse/_typehints.py", line 952, in adapt_typehints
    val = adapt_class_type(val, serialize, instantiate_classes, sub_add_kwargs, prev_val=prev_val)
  File "/opt/conda/envs/asvspoof5/lib/python3.10/site-packages/jsonargparse/_typehints.py", line 1173, in adapt_class_type
    init_args = parser.instantiate_classes(init_args)
  File "/opt/conda/envs/asvspoof5/lib/python3.10/site-packages/jsonargparse/_deprecated.py", line 141, in patched_instantiate_classes
    cfg = self._unpatched_instantiate_classes(cfg, **kwargs)
  File "/opt/conda/envs/asvspoof5/lib/python3.10/site-packages/jsonargparse/_core.py", line 1187, in instantiate_classes
    parent[key] = component.instantiate_classes(value)
  File "/opt/conda/envs/asvspoof5/lib/python3.10/site-packages/jsonargparse/_typehints.py", line 565, in instantiate_classes
    value[num] = adapt_typehints(
  File "/opt/conda/envs/asvspoof5/lib/python3.10/site-packages/jsonargparse/_typehints.py", line 952, in adapt_typehints
    val = adapt_class_type(val, serialize, instantiate_classes, sub_add_kwargs, prev_val=prev_val)
  File "/opt/conda/envs/asvspoof5/lib/python3.10/site-packages/jsonargparse/_typehints.py", line 1187, in adapt_class_type
    return instantiator_fn(val_class, **{**init_args, **dict_kwargs})
  File "/opt/conda/envs/asvspoof5/lib/python3.10/site-packages/jsonargparse/_common.py", line 148, in default_class_instantiator
    return class_type(*args, **kwargs)
  File "/opt/conda/envs/asvspoof5/lib/python3.10/site-packages/torchaudio/transforms/_transforms.py", line 593, in __init__
    self.spectrogram = Spectrogram(
  File "/opt/conda/envs/asvspoof5/lib/python3.10/site-packages/torchaudio/transforms/_transforms.py", line 85, in __init__
    window = window_fn(self.win_length) if wkwargs is None else window_fn(self.win_length, **wkwargs)
TypeError: 'str' object is not callable
```

I have traced the error for using default argument. I found the `window_fn` is a string `torch._VariableFunctionsClass.hann_window`. It seems the import path is treated as a string. But even though this path is imported, it will raise an error since the window function should be imported as `from torch import hann_window` or `import torch.hann_window`, rather than `import torch._VariableFunctionsClass.hann_window`.

### Environment

- jsonargparse version: 4.28
- Python version: 3.10
- How jsonargparse was installed: pip install "jsonargparse[all]"
- OS: Ubuntu18.04


Discussion:
sonarcloud[bot]: ## [![Quality Gate Passed](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/QualityGateBadge/qg-passed-20px.png 'Quality Gate Passed')](https://sonarcloud.io/dashboard?id=omni-us_jsonargparse&pullRequest=535) **Quality Gate passed**  
Issues  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0 New issues](https://sonarcloud.io/project/issues?id=omni-us_jsonargparse&pullRequest=535&resolved=false&sinceLeakPeriod=true)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/accepted-16px.png '') [0 Accepted issues](https://sonarcloud.io/project/issues?id=omni-us_jsonargparse&pullRequest=535&resolutions=WONTFIX)

Measures  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0 Security Hotspots](https://sonarcloud.io/project/security_hotspots?id=omni-us_jsonargparse&pullRequest=535&resolved=false&sinceLeakPeriod=true)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0.0% Coverage on New Code](https://sonarcloud.io/component_measures?id=omni-us_jsonargparse&pullRequest=535&metric=new_coverage&view=list)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0.0% Duplication on New Code](https://sonarcloud.io/component_measures?id=omni-us_jsonargparse&pullRequest=535&metric=new_duplicated_lines_density&view=list)  
  
[See analysis details on SonarCloud](https://sonarcloud.io/dashboard?id=omni-us_jsonargparse&pullRequest=535)


sonarcloud[bot]: ## [![Quality Gate Passed](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/QualityGateBadge/qg-passed-20px.png 'Quality Gate Passed')](https://sonarcloud.io/dashboard?id=omni-us_jsonargparse&pullRequest=535) **Quality Gate passed**  
Issues  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0 New issues](https://sonarcloud.io/project/issues?id=omni-us_jsonargparse&pullRequest=535&resolved=false&sinceLeakPeriod=true)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/accepted-16px.png '') [0 Accepted issues](https://sonarcloud.io/project/issues?id=omni-us_jsonargparse&pullRequest=535&resolutions=WONTFIX)

Measures  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0 Security Hotspots](https://sonarcloud.io/project/security_hotspots?id=omni-us_jsonargparse&pullRequest=535&resolved=false&sinceLeakPeriod=true)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0.0% Coverage on New Code](https://sonarcloud.io/component_measures?id=omni-us_jsonargparse&pullRequest=535&metric=new_coverage&view=list)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0.0% Duplication on New Code](https://sonarcloud.io/component_measures?id=omni-us_jsonargparse&pullRequest=535&metric=new_duplicated_lines_density&view=list)  
  
[See analysis details on SonarCloud](https://sonarcloud.io/dashboard?id=omni-us_jsonargparse&pullRequest=535)


codecov[bot]: ## [Codecov](https://app.codecov.io/gh/omni-us/jsonargparse/pull/535?dropdown=coverage&src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=omni-us) Report
All modified and coverable lines are covered by tests :white_check_mark:
> Project coverage is 99.89%. Comparing base [(`2bcbd48`)](https://app.codecov.io/gh/omni-us/jsonargparse/commit/2bcbd48b2c2de85180b81cd9de8c0b985d49442f?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=omni-us) to head [(`d4ac134`)](https://app.codecov.io/gh/omni-us/jsonargparse/commit/d4ac134bbd1a48689088b41942f776a640f902c7?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=omni-us).

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


```diff
@@             Coverage Diff             @@
##              main     #535      +/-   ##
===========================================
- Coverage   100.00%   99.89%   -0.11%     
===========================================
  Files           22       22              
  Lines         6374     6377       +3     
===========================================
- Hits          6374     6370       -4     
- Misses           0        7       +7     
```

| [Flag](https://app.codecov.io/gh/omni-us/jsonargparse/pull/535/flags?src=pr&el=flags&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=omni-us) | Coverage Δ | |
|---|---|---|
| [py3.10](https://app.codecov.io/gh/omni-us/jsonargparse/pull/535/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=omni-us) | `?` | |
| [py3.10_all](https://app.codecov.io/gh/omni-us/jsonargparse/pull/535/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=omni-us) | `?` | |
| [py3.10_pydantic1](https://app.codecov.io/gh/omni-us/jsonargparse/pull/535/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=omni-us) | `48.56% <20.00%> (-0.03%)` | :arrow_down: |
| [py3.10_pydantic2](https://app.codecov.io/gh/omni-us/jsonargparse/pull/535/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=omni-us) | `48.34% <20.00%> (-0.03%)` | :arrow_down: |
| [py3.10_types](https://app.codecov.io/gh/omni-us/jsonargparse/pull/535/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=omni-us) | `?` | |
| [py3.11](https://app.codecov.io/gh/omni-us/jsonargparse/pull/535/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=omni-us) | `85.63% <100.00%> (+<0.01%)` | :arrow_up: |
| [py3.11_all](https://app.codecov.io/gh/omni-us/jsonargparse/pull/535/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=omni-us) | `98.68% <100.00%> (+<0.01%)` | :arrow_up: |
| [py3.11_types](https://app.codecov.io/gh/omni-us/jsonargparse/pull/535/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=omni-us) | `98.69% <100.00%> (+<0.01%)` | :arrow_up: |
| [py3.12](https://app.codecov.io/gh/omni-us/jsonargparse/pull/535/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=omni-us) | `85.77% <100.00%> (+<0.01%)` | :arrow_up: |
| [py3.12_all](https://app.codecov.io/gh/omni-us/jsonargparse/pull/535/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=omni-us) | `98.68% <100.00%> (+<0.01%)` | :arrow_up: |
| [py3.12_types](https://app.codecov.io/gh/omni-us/jsonargparse/pull/535/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=omni-us) | `?` | |
| [py3.7](https://app.codecov.io/gh/omni-us/jsonargparse/pull/535/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=omni-us) | `86.12% <100.00%> (+<0.01%)` | :arrow_up: |
| [py3.7_all](https://app.codecov.io/gh/omni-us/jsonargparse/pull/535/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=omni-us) | `99.24% <100.00%> (+<0.01%)` | :arrow_up: |
| [py3.7_types](https://app.codecov.io/gh/omni-us/jsonargparse/pull/535/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=omni-us) | `99.27% <100.00%> (+<0.01%)` | :arrow_up: |
| [py3.8](https://app.codecov.io/gh/omni-us/jsonargparse/pull/535/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=omni-us) | `?` | |
| [py3.8_all](https://app.codecov.io/gh/omni-us/jsonargparse/pull/535/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=omni-us) | `?` | |
| [py3.8_types](https://app.codecov.io/gh/omni-us/jsonargparse/pull/535/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=omni-us) | `?` | |
| [py3.9](https://app.codecov.io/gh/omni-us/jsonargparse/pull/535/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=omni-us) | `?` | |
| [py3.9_all](https://app.codecov.io/gh/omni-us/jsonargparse/pull/535/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=omni-us) | `?` | |
| [py3.9_types](https://app.codecov.io/gh/omni-us/jsonargparse/pull/535/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=omni-us) | `?` | |

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.

</details>

[:umbrella: View full report in Codecov by Sentry](https://app.codecov.io/gh/omni-us/jsonargparse/pull/535?dropdown=coverage&src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=omni-us).   
:loudspeaker: Have feedback on the report? [Share it here](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 46a65d7..207108c 100644
--- a/CHANGELOG.rst
+++ b/CHANGELOG.rst
@@ -15,6 +15,11 @@ paths are considered internals and can change in minor and patch releases.
 v4.31.0 (2024-06-??)
 --------------------
 
+Fixed
+^^^^^
+- Resolving of import paths for some ``torch`` functions not working (`#535
+  <https://github.com/omni-us/jsonargparse/pull/535>`__).
+
 Changed
 ^^^^^^^
 - Now ``--*.help`` output shows options without ``init_args`` (`#533
diff --git a/jsonargparse/_util.py b/jsonargparse/_util.py
index 9176791..f07d087 100644
--- a/jsonargparse/_util.py
+++ b/jsonargparse/_util.py
@@ -233,7 +233,7 @@ def get_import_path(value: Any) -> Optional[str]:
     if not path:
         raise ValueError(f"Not possible to determine the import path for object {value}.")
 
-    if qualname and module_path and "." in module_path:
+    if qualname and module_path and ("." in qualname or "." in module_path):
         module_parts = module_path.split(".")
         for num in range(len(module_parts)):
             module_path = ".".join(module_parts[: num + 1])
@@ -241,7 +241,10 @@ def get_import_path(value: Any) -> Optional[str]:
             if "." in qualname:
                 obj_name, attr = qualname.rsplit(".", 1)
                 obj = getattr(module, obj_name, None)
-                if getattr(obj, attr, None) is value:
+                if getattr(module, attr, None) is value:
+                    path = module_path + "." + attr
+                    break
+                elif getattr(obj, attr, None) is value:
                     path = module_path + "." + qualname
                     break
             elif getattr(module, qualname, None) is value:


Test patch:
diff --git a/jsonargparse_tests/test_util.py b/jsonargparse_tests/test_util.py
index 3c0aef3..c7709ac 100644
--- a/jsonargparse_tests/test_util.py
+++ b/jsonargparse_tests/test_util.py
@@ -533,8 +533,12 @@ def test_logger_jsonargparse_debug():
 
 
 def test_import_object_invalid():
-    pytest.raises(ValueError, lambda: import_object(True))
-    pytest.raises(ValueError, lambda: import_object("jsonargparse-tests.os"))
+    with pytest.raises(ValueError) as ctx:
+        import_object(True)
+    ctx.match("Expected a dot import path string")
+    with pytest.raises(ValueError) as ctx:
+        import_object("jsonargparse-tests.os")
+    ctx.match("Unexpected import path format")
 
 
 def test_get_import_path():
@@ -548,6 +552,19 @@ def test_get_import_path():
     assert get_import_path(MISSING) == "dataclasses.MISSING"
 
 
+class _StaticMethods:
+    @staticmethod
+    def static_method():
+        pass
+
+
+static_method = _StaticMethods.static_method
+
+
+def test_get_import_path_static_method_shorthand():
+    assert get_import_path(static_method) == f"{__name__}.static_method"
+
+
 def unresolvable_import():
     pass