Repository: omni-us/jsonargparse
Pull request #162 (2022-09-26 05:41:18)

Fix some issues identified together
## What does this PR do?

As the title says.

## Before submitting

- [x] Did you read the [contributing guideline](https://github.com/omni-us/jsonargparse/blob/master/CONTRIBUTING.rst)?
- [n/a] Did you update **the documentation**? (readme and public docstrings)
- [x] Did you write **unit tests** such that there is 100% coverage on related code? (required for bug fixes and new features)
- [x] Did you verify that new and existing **tests pass locally**?
- [x] Did you make sure that all changes preserve **backward compatibility**?
- [x] Did you update **the CHANGELOG**? (not for typos, docs, test updates, or minor internal changes/refactors)


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=162)

[![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=162&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=162&resolved=false&types=BUG) [0 Bugs](https://sonarcloud.io/project/issues?id=omni-us_jsonargparse&pullRequest=162&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=162&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=162&resolved=false&types=VULNERABILITY) [0 Vulnerabilities](https://sonarcloud.io/project/issues?id=omni-us_jsonargparse&pullRequest=162&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=162&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=162&resolved=false&types=SECURITY_HOTSPOT) [0 Security Hotspots](https://sonarcloud.io/project/security_hotspots?id=omni-us_jsonargparse&pullRequest=162&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=162&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=162&resolved=false&types=CODE_SMELL) [1 Code Smell](https://sonarcloud.io/project/issues?id=omni-us_jsonargparse&pullRequest=162&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=162) 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=162&metric=new_duplicated_lines_density&view=list) [0.0% Duplication](https://sonarcloud.io/component_measures?id=omni-us_jsonargparse&pullRequest=162&metric=new_duplicated_lines_density&view=list)


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=162)

[![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=162&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=162&resolved=false&types=BUG) [0 Bugs](https://sonarcloud.io/project/issues?id=omni-us_jsonargparse&pullRequest=162&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=162&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=162&resolved=false&types=VULNERABILITY) [0 Vulnerabilities](https://sonarcloud.io/project/issues?id=omni-us_jsonargparse&pullRequest=162&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=162&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=162&resolved=false&types=SECURITY_HOTSPOT) [0 Security Hotspots](https://sonarcloud.io/project/security_hotspots?id=omni-us_jsonargparse&pullRequest=162&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=162&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=162&resolved=false&types=CODE_SMELL) [1 Code Smell](https://sonarcloud.io/project/issues?id=omni-us_jsonargparse&pullRequest=162&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=162) 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=162&metric=new_duplicated_lines_density&view=list) [0.0% Duplication](https://sonarcloud.io/component_measures?id=omni-us_jsonargparse&pullRequest=162&metric=new_duplicated_lines_density&view=list)


codecov-commenter: # [Codecov](https://codecov.io/gh/omni-us/jsonargparse/pull/162?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 [(`f03deda`)](https://codecov.io/gh/omni-us/jsonargparse/pull/162?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=omni-us) compared to base [(`00c7f96`)](https://codecov.io/gh/omni-us/jsonargparse/commit/00c7f96d924503f96b70cc29b812f45ece077e0c?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      #162   +/-   ##
=========================================
  Coverage   100.00%   100.00%           
=========================================
  Files           17        17           
  Lines         4263      4276   +13     
=========================================
+ Hits          4263      4276   +13     
```

| Flag | Coverage Δ | |
|---|---|---|
| py3.10_all | `99.92% <100.00%> (+<0.01%)` | :arrow_up: |
| py3.6 | `90.72% <61.11%> (-0.21%)` | :arrow_down: |
| py3.6_all | `99.71% <100.00%> (+<0.01%)` | :arrow_up: |
| py3.7_all | `99.83% <100.00%> (+<0.01%)` | :arrow_up: |
| py3.8 | `90.99% <61.11%> (-0.21%)` | :arrow_down: |
| py3.8_all | `99.90% <100.00%> (+<0.01%)` | :arrow_up: |
| py3.9_all | `99.90% <100.00%> (+<0.01%)` | :arrow_up: |

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/162?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=omni-us) | Coverage Δ | |
|---|---|---|
| [jsonargparse/core.py](https://codecov.io/gh/omni-us/jsonargparse/pull/162/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=omni-us#diff-anNvbmFyZ3BhcnNlL2NvcmUucHk=) | `100.00% <100.00%> (ø)` | |
| [jsonargparse/jsonnet.py](https://codecov.io/gh/omni-us/jsonargparse/pull/162/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=omni-us#diff-anNvbmFyZ3BhcnNlL2pzb25uZXQucHk=) | `100.00% <100.00%> (ø)` | |
| [jsonargparse/jsonschema.py](https://codecov.io/gh/omni-us/jsonargparse/pull/162/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=omni-us#diff-anNvbmFyZ3BhcnNlL2pzb25zY2hlbWEucHk=) | `100.00% <100.00%> (ø)` | |
| [jsonargparse/parameter\_resolvers.py](https://codecov.io/gh/omni-us/jsonargparse/pull/162/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=omni-us#diff-anNvbmFyZ3BhcnNlL3BhcmFtZXRlcl9yZXNvbHZlcnMucHk=) | `100.00% <100.00%> (ø)` | |
| [jsonargparse/signatures.py](https://codecov.io/gh/omni-us/jsonargparse/pull/162/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=omni-us#diff-anNvbmFyZ3BhcnNlL3NpZ25hdHVyZXMucHk=) | `100.00% <100.00%> (ø)` | |
| [jsonargparse/typehints.py](https://codecov.io/gh/omni-us/jsonargparse/pull/162/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/162?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).

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=162)

[![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=162&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=162&resolved=false&types=BUG) [0 Bugs](https://sonarcloud.io/project/issues?id=omni-us_jsonargparse&pullRequest=162&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=162&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=162&resolved=false&types=VULNERABILITY) [0 Vulnerabilities](https://sonarcloud.io/project/issues?id=omni-us_jsonargparse&pullRequest=162&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=162&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=162&resolved=false&types=SECURITY_HOTSPOT) [0 Security Hotspots](https://sonarcloud.io/project/security_hotspots?id=omni-us_jsonargparse&pullRequest=162&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=162&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=162&resolved=false&types=CODE_SMELL) [1 Code Smell](https://sonarcloud.io/project/issues?id=omni-us_jsonargparse&pullRequest=162&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=162) 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=162&metric=new_duplicated_lines_density&view=list) [0.0% Duplication](https://sonarcloud.io/component_measures?id=omni-us_jsonargparse&pullRequest=162&metric=new_duplicated_lines_density&view=list)



Patch:
diff --git a/CHANGELOG.rst b/CHANGELOG.rst
index e85299e..8c41fe5 100644
--- a/CHANGELOG.rst
+++ b/CHANGELOG.rst
@@ -17,15 +17,28 @@ paths are considered internals and can change in any moment.
 v4.15.0 (2022-09-??)
 --------------------
 
+Added
+^^^^^
+- ``set_defaults`` now supports subclass by name and normalization of import path.
+
 Fixed
 ^^^^^
 - Loop variable capture bug pointed out by lgtm.com.
+- Issue with discard ``init_args`` when ``class_path`` not a subclass.
+- No error shown when arguments given to class group that does not accept arguments `#161
+  <https://github.com/omni-us/jsonargparse/issues/161#issuecomment-1256973565>`__.
+- Incorrect replacement of ``**kwargs`` when ``*args`` present in parameter resolver.
+- Override of ``class_path`` not discarding ``init_args`` when loading from
+  config file.
+- Invalid values given to the ``compute_fn`` of a argument link applied on parse
+  without showing an understandable error message.
 
 Changed
 ^^^^^^^
 - Now ``UUID`` and ``timedelta`` types are registered on first use to avoid
   possibly unused imports.
 - json/yaml dump sort now defaults to false for all python implementations.
+- ``add_class_arguments`` will not add config load option if no added arguments.
 
 
 v4.14.1 (2022-09-26)
diff --git a/jsonargparse/actions.py b/jsonargparse/actions.py
index 9e238c9..ce662c9 100644
--- a/jsonargparse/actions.py
+++ b/jsonargparse/actions.py
@@ -175,8 +175,6 @@ class ActionConfigFile(Action, FilesCompleterMethod):
     @staticmethod
     def apply_config(parser, cfg, dest, value) -> None:
         with _ActionSubCommands.not_single_subcommand(), previous_config_context(cfg):
-            if cfg.get(dest) is None:
-                cfg[dest] = []
             kwargs = {'env': False, 'defaults': False, '_skip_check': True, '_fail_no_subcommand': False}
             try:
                 cfg_path: Optional[Path] = Path(value, mode=get_config_read_mode())
@@ -190,10 +188,11 @@ class ActionConfigFile(Action, FilesCompleterMethod):
                     raise TypeError(f'Parser key "{dest}": {ex_str}') from ex_str
             else:
                 cfg_file = parser.parse_path(value, **kwargs)
+            cfg_merged = parser.merge_config(cfg_file, cfg)
+            cfg.__dict__.update(cfg_merged.__dict__)
+            if cfg.get(dest) is None:
+                cfg[dest] = []
             cfg[dest].append(cfg_path)
-            cfg.update(cfg_file)
-            from .typehints import ActionTypeHint
-            ActionTypeHint.apply_appends(parser, cfg)
 
 
 previous_config: ContextVar = ContextVar('previous_config', default=None)
diff --git a/jsonargparse/core.py b/jsonargparse/core.py
index 1dc510c..0d51a36 100644
--- a/jsonargparse/core.py
+++ b/jsonargparse/core.py
@@ -11,11 +11,11 @@ from copy import deepcopy
 from typing import Any, Callable, Dict, List, NoReturn, Optional, Sequence, Set, Tuple, Type, Union
 
 from .formatters import DefaultHelpFormatter, empty_help, formatter_context, get_env_var
-from .jsonnet import ActionJsonnet
+from .jsonnet import ActionJsonnet, ActionJsonnetExtVars
 from .jsonschema import ActionJsonSchema
 from .link_arguments import ActionLink, ArgumentLinking
 from .loaders_dumpers import check_valid_dump_format, dump_using_format, get_loader_exceptions, loaders, load_value, load_value_context, yaml_load
-from .namespace import dict_to_namespace, is_meta_key, Namespace, patch_namespace, split_key, split_key_leaf, strip_meta
+from .namespace import is_meta_key, Namespace, patch_namespace, split_key, split_key_leaf, strip_meta
 from .signatures import is_pure_dataclass, SignatureArguments
 from .typehints import ActionTypeHint, is_subclass_spec
 from .typing import is_final_class
@@ -1039,6 +1039,8 @@ class ArgumentParser(ActionsContainer, ArgumentLinking, argparse.ArgumentParser)
                     except TypeError as ex:
                         if not (val == {} and ActionTypeHint.is_subclass_typehint(action) and key not in self.required_args):
                             raise ex
+                elif key in self.groups and hasattr(self.groups[key], 'instantiate_class'):
+                    raise TypeError(f'Class group {key!r} got an unexpected value: {val}.')
                 else:
                     raise KeyError(f'No action for destination key "{key}" to check its value.')
 
@@ -1192,17 +1194,25 @@ class ArgumentParser(ActionsContainer, ArgumentLinking, argparse.ArgumentParser)
             keys = list(cfg.__dict__.keys())
 
         if prev_cfg:
-            prev_cfg = prev_cfg.clone().update(dict_to_namespace(cfg))
+            prev_cfg = prev_cfg.clone()
         else:
-            prev_cfg = cfg
+            prev_cfg = Namespace()
 
         config_keys: Set[str] = set()
         num = 0
         while num < len(keys):
             key = keys[num]
-            num += 1
             exclude = _ActionConfigLoad if key in config_keys else None
             action, subcommand = _find_action_and_subcommand(self, key, exclude=exclude)
+
+            if isinstance(action, ActionJsonnet):
+                ext_vars_key = action._ext_vars
+                if ext_vars_key and ext_vars_key not in keys[:num]:
+                    keys = keys[:num] + [ext_vars_key] + [k for k in keys[num:] if k != ext_vars_key]
+                    continue
+
+            num += 1
+
             if action is None or isinstance(action, _ActionSubCommands):
                 value = cfg[key]
                 if isinstance(value, dict):
@@ -1212,6 +1222,7 @@ class ArgumentParser(ActionsContainer, ArgumentLinking, argparse.ArgumentParser)
                     keys += [key+'.'+k for k in new_keys if key+'.'+k not in keys]
                 cfg[key] = value
                 continue
+
             action_dest = action.dest if subcommand is None else subcommand+'.'+action.dest
             value = cfg[action_dest]
             with lenient_check_context(), load_value_context(self.parser_mode):
@@ -1219,6 +1230,8 @@ class ArgumentParser(ActionsContainer, ArgumentLinking, argparse.ArgumentParser)
             if isinstance(action, _ActionConfigLoad):
                 config_keys.add(action_dest)
                 keys.append(action_dest)
+            elif isinstance(action, ActionJsonnetExtVars):
+                prev_cfg[action_dest] = value
             cfg[action_dest] = value
         return cfg[parent_key] if parent_key else cfg
 
@@ -1240,7 +1253,7 @@ class ArgumentParser(ActionsContainer, ArgumentLinking, argparse.ArgumentParser)
         return cfg
 
 
-    def _check_value_key(self, action: argparse.Action, value: Any, key: str, cfg: Namespace) -> Any:
+    def _check_value_key(self, action: argparse.Action, value: Any, key: str, cfg: Optional[Namespace]) -> Any:
         """Checks the value for a given action.
 
         Args:
@@ -1261,13 +1274,7 @@ class ArgumentParser(ActionsContainer, ArgumentLinking, argparse.ArgumentParser)
             subparser.check_config(value)
         elif isinstance(action, _ActionConfigLoad):
             if isinstance(value, str):
-                fpath = None
-                if '.' in key:
-                    parent = cfg.get(split_key_leaf(key)[0])
-                    if isinstance(parent, Namespace):
-                        fpath = parent.get('__path__')
-                with change_to_path_dir(fpath):
-                    value = action.check_type(value, self)
+                value = action.check_type(value, self)
         elif hasattr(action, '_check_type'):
             value = action._check_type(value, cfg=cfg)  # type: ignore
         elif action.type is not None:
diff --git a/jsonargparse/jsonnet.py b/jsonargparse/jsonnet.py
index 313ea08..219c76a 100644
--- a/jsonargparse/jsonnet.py
+++ b/jsonargparse/jsonnet.py
@@ -1,8 +1,8 @@
 """Actions to support jsonnet."""
 
 import json
-from typing import Optional, Union, Dict, Tuple, Any
 from argparse import Action
+from typing import Any, Dict, Optional, Tuple, Union
 
 from .actions import _is_action_value_list
 from .jsonschema import ActionJsonSchema
@@ -27,13 +27,16 @@ class ActionJsonnetExtVars(ActionJsonSchema):
 
     def __init__(self, **kwargs):
         """Initializer for ActionJsonnetExtVars instance."""
-        super().__init__(schema={'type': 'object'}, with_meta=False)
+        if kwargs:
+            super().__init__(**kwargs)
+        else:
+            super().__init__(schema={'type': 'object'}, with_meta=False)
 
 
     def __call__(self, *args, **kwargs):
         if len(args) == 0 and 'default' not in kwargs:
             kwargs['default'] = {}
-        return super().__call__(*args, **kwargs)
+        return super().__call__(*args, _class_type=ActionJsonnetExtVars, **kwargs)
 
 
 class ActionJsonnet(Action):
@@ -41,9 +44,9 @@ class ActionJsonnet(Action):
 
     def __init__(
         self,
-        ext_vars: str = None,
-        schema: Union[str, Dict] = None,
-        **kwargs
+        ext_vars: Optional[str] = None,
+        schema: Optional[Union[str, Dict]] = None,
+        **kwargs,
     ):
         """Initializer for ActionJsonnet instance.
 
@@ -95,7 +98,9 @@ class ActionJsonnet(Action):
 
     def _check_type(self, value, cfg):
         islist = _is_action_value_list(self)
-        ext_vars = cfg.get(self._ext_vars, {})
+        ext_vars = {}
+        if cfg:
+            ext_vars = cfg.get(self._ext_vars, {})
         if not islist:
             value = [value]
         for num, val in enumerate(value):
diff --git a/jsonargparse/jsonschema.py b/jsonargparse/jsonschema.py
index fcaf15b..bdf52bb 100644
--- a/jsonargparse/jsonschema.py
+++ b/jsonargparse/jsonschema.py
@@ -73,7 +73,8 @@ class ActionJsonSchema(Action):
             kwargs['_with_meta'] = self._with_meta
             if 'help' in kwargs and isinstance(kwargs['help'], str) and '%s' in kwargs['help']:
                 kwargs['help'] = kwargs['help'] % json.dumps(self._validator.schema, sort_keys=True)
-            return ActionJsonSchema(**kwargs)
+            class_type = kwargs.pop('_class_type', ActionJsonSchema)
+            return class_type(**kwargs)
         val = self._check_type(args[2])
         if not self._with_meta:
             val = strip_meta(val)
diff --git a/jsonargparse/link_arguments.py b/jsonargparse/link_arguments.py
index ddd802d..67d47fd 100644
--- a/jsonargparse/link_arguments.py
+++ b/jsonargparse/link_arguments.py
@@ -5,7 +5,7 @@ import re
 from argparse import Action, SUPPRESS
 from collections import defaultdict
 from typing import Any, Callable, List, Optional, Tuple, Type, Union
-from .actions import _ActionConfigLoad, _ActionSubCommands, ActionConfigFile, filter_default_actions, _find_parent_action
+from .actions import _ActionConfigLoad, _ActionSubCommands, ActionConfigFile, filter_default_actions, _find_action, _find_parent_action
 from .namespace import Namespace, split_key_leaf
 from .type_checking import ArgumentParser, _ArgumentGroup
 
@@ -207,6 +207,7 @@ class ActionLink(Action):
                 for source_key, source_action in action.source:
                     if ActionTypeHint.is_subclass_typehint(source_action[0]) and source_key not in cfg:
                         parser.logger.debug(f'Link {action.option_strings[0]} ignored since source {source_action[0]._typehint} does not have that parameter.')
+                    parser._check_value_key(source_action[0], cfg[source_action[0].dest], source_action[0].dest, None)
                     args.append(cfg[source_key])
             except KeyError:
                 continue
diff --git a/jsonargparse/parameter_resolvers.py b/jsonargparse/parameter_resolvers.py
index bc42587..ba045f5 100644
--- a/jsonargparse/parameter_resolvers.py
+++ b/jsonargparse/parameter_resolvers.py
@@ -257,7 +257,7 @@ def replace_args_and_kwargs(params: ParamList, args: ParamList, kwargs: ParamLis
     kwargs_idx = get_arg_kind_index(params, kinds.VAR_KEYWORD)
     if args_idx >= 0:
         params = params[:args_idx] + args + params[args_idx+1:]
-        if kwargs_idx != 1:
+        if kwargs_idx >= 0:
             kwargs_idx += len(args) - 1
     if kwargs_idx >= 0:
         existing_names = set(p.name for p in params[:kwargs_idx] + params[kwargs_idx+1:])
diff --git a/jsonargparse/signatures.py b/jsonargparse/signatures.py
index 13c93ca..d11874f 100644
--- a/jsonargparse/signatures.py
+++ b/jsonargparse/signatures.py
@@ -221,7 +221,14 @@ class SignatureArguments(LoggerProperty):
         ## Create group if requested ##
         doc_group = get_doc_short_description(function_or_class, method_name, logger=self.logger)
         component = getattr(function_or_class, method_name) if method_name else function_or_class
-        group = self._create_group_if_requested(component, nested_key, as_group, doc_group, instantiate=instantiate)
+        group = self._create_group_if_requested(
+            component,
+            nested_key,
+            as_group,
+            doc_group,
+            config_load=len(params) > 0,
+            instantiate=instantiate,
+        )
 
         ## Add parameter arguments ##
         added_args: List[str] = []
diff --git a/jsonargparse/typehints.py b/jsonargparse/typehints.py
index 9a865e0..d356a2e 100644
--- a/jsonargparse/typehints.py
+++ b/jsonargparse/typehints.py
@@ -144,6 +144,7 @@ class ActionTypeHint(Action):
             self.default = default.lazy_get_init_data()
         elif self.is_subclass_typehint(self._typehint, all_subtypes=False) and isinstance(default, dict) and 'class_path' in default:
             self.default = subclass_spec_as_namespace(default)
+            self.default.class_path = normalize_import_path(self.default.class_path, self._typehint)
         elif is_enum_type(self._typehint) and isinstance(default, Enum):
             self.default = default.name
         elif is_callable_type(self._typehint) and callable(default) and not inspect.isclass(default):
@@ -745,6 +746,12 @@ def resolve_class_path_by_name(cls: Type, name: str) -> str:
     return class_path
 
 
+def normalize_import_path(class_path, typehint):
+    if '.' not in class_path:
+        class_path = resolve_class_path_by_name(typehint, class_path)
+    return get_import_path(import_object(class_path))
+
+
 dump_kwargs: ContextVar = ContextVar('dump_kwargs', default={})
 
 
@@ -755,17 +762,17 @@ def dump_kwargs_context(kwargs):
 
 
 def discard_init_args_on_class_path_change(parser_or_action, prev_val, value):
-    if prev_val and 'init_args' in prev_val and prev_val['class_path'] != value.class_path:
+    if prev_val and 'init_args' in prev_val and prev_val['class_path'] != value['class_path']:
         parser = parser_or_action
         if isinstance(parser_or_action, ActionTypeHint):
             sub_add_kwargs = getattr(parser_or_action, 'sub_add_kwargs', {})
-            parser = ActionTypeHint.get_class_parser(value.class_path, sub_add_kwargs)
+            parser = ActionTypeHint.get_class_parser(value['class_path'], sub_add_kwargs)
         prev_val = subclass_spec_as_namespace(prev_val)
         del_args = {}
         for key, val in list(prev_val.init_args.__dict__.items()):
             action = _find_action(parser, key)
             if action:
-                with lenient_check_context(lenient=False):
+                with lenient_check_context(lenient=False), load_value_context(parser.parser_mode):
                     try:
                         parser._check_value_key(action, val, key, Namespace())
                     except Exception:
@@ -774,8 +781,8 @@ def discard_init_args_on_class_path_change(parser_or_action, prev_val, value):
                 del_args[key] = prev_val.init_args.pop(key)
         if del_args:
             warnings.warn(
-                f'Due to class_path change from {prev_val.class_path!r} to {value.class_path!r}, '
-                f'discarding init_args: {del_args}.'
+                f"Due to class_path change from {prev_val['class_path']!r} to {value['class_path']!r}, "
+                f"discarding init_args: {del_args}."
             )
 
 


Test patch:
diff --git a/jsonargparse_tests/test_jsonnet.py b/jsonargparse_tests/test_jsonnet.py
index a09ddb0..f148f66 100755
--- a/jsonargparse_tests/test_jsonnet.py
+++ b/jsonargparse_tests/test_jsonnet.py
@@ -2,8 +2,10 @@
 
 import json
 import os
+import pathlib
 import re
 import unittest
+import yaml
 from io import StringIO
 from jsonargparse import (
     ActionConfigFile,
@@ -161,8 +163,7 @@ class JsonnetTests(TempDirTestCase):
             action=ActionConfigFile)
 
         jsonnet_file = os.path.join(self.tmpdir, 'example.jsonnet')
-        with open(jsonnet_file, 'w') as output_file:
-            output_file.write(example_2_jsonnet)
+        pathlib.Path(jsonnet_file).write_text(example_2_jsonnet)
         outdir = os.path.join(self.tmpdir, 'output')
         outyaml = os.path.join(outdir, 'main.yaml')
         outjsonnet = os.path.join(outdir, 'example.jsonnet')
@@ -170,8 +171,13 @@ class JsonnetTests(TempDirTestCase):
 
         cfg = parser.parse_args(['--ext_vars', '{"param": 123}', '--jsonnet', jsonnet_file])
         self.assertEqual(str(cfg.jsonnet['__path__']), jsonnet_file)
-
         parser.save(cfg, outyaml)
+
+        outyaml_path = pathlib.Path(outyaml)
+        main_cfg = yaml.safe_load(outyaml_path.read_text())
+        main_cfg = {k: main_cfg[k] for k in ['jsonnet', 'ext_vars']}  # Make sure ext_vars after jsonnet
+        outyaml_path.write_text(yaml.safe_dump(main_cfg, sort_keys=False))
+
         cfg2 = parser.parse_args(['--cfg', outyaml])
         cfg2.cfg = None
         self.assertTrue(os.path.isfile(outyaml))
diff --git a/jsonargparse_tests/test_link_arguments.py b/jsonargparse_tests/test_link_arguments.py
index f853b1e..e78ae65 100755
--- a/jsonargparse_tests/test_link_arguments.py
+++ b/jsonargparse_tests/test_link_arguments.py
@@ -37,6 +37,23 @@ class LinkArgumentsTests(unittest.TestCase):
         self.assertEqual(dump, {'a': {'v1': 2, 'v2': -5}})
 
 
+    def test_link_arguments_on_parse_invalid_compute_fn_arg(self):
+        parser = ArgumentParser(error_handler=None)
+        parser.add_argument('--cal1', type=Calendar, default=lazy_instance(TextCalendar))
+        parser.add_argument('--cal2', type=Calendar, default=lazy_instance(Calendar))
+        parser.link_arguments(
+            'cal1',
+            'cal2.init_args.firstweekday',
+            compute_fn=lambda c: c.init_args.firstweekday+1,
+        )
+        cfg = parser.parse_args(['--cal1.init_args.firstweekday=2'])
+        self.assertEqual(cfg.cal1.init_args.firstweekday, 2)
+        self.assertEqual(cfg.cal2.init_args.firstweekday, 3)
+        with self.assertRaises(ParserError) as cm:
+            parser.parse_args(['--cal1.class_path.init_args.firstweekday=2'])
+        self.assertIn('Parser key "cal1"', str(cm.exception))
+
+
     def test_link_arguments_on_parse_add_class_arguments(self):
         class ClassA:
             def __init__(self, v1: int = 2, v2: int = 3):
diff --git a/jsonargparse_tests/test_signatures.py b/jsonargparse_tests/test_signatures.py
index 1d001ea..b9f142c 100755
--- a/jsonargparse_tests/test_signatures.py
+++ b/jsonargparse_tests/test_signatures.py
@@ -152,6 +152,29 @@ class SignaturesTests(unittest.TestCase):
         self.assertRaises(ValueError, lambda: parser.add_class_arguments(Class2))
 
 
+    def test_add_class_without_args(self):
+        class NoArgs:
+            pass
+
+        parser = ArgumentParser(error_handler=None)
+        parser.add_argument('--cfg', action=ActionConfigFile)
+        parser.add_class_arguments(NoArgs, 'noargs')
+
+        help_str = StringIO()
+        parser.print_help(help_str)
+        self.assertNotIn('noargs', help_str.getvalue())
+
+        cfg = parser.parse_args([])
+        self.assertNotIn('noargs', cfg)
+        init = parser.instantiate_classes(cfg)
+        self.assertIsInstance(init.noargs, NoArgs)
+
+        with mock_module(NoArgs) as module:
+            config = {'noargs': {'class_path': f'{module}.NoArgs'}}
+            with self.assertRaises(ParserError):
+                parser.parse_args([f'--cfg={config}'])
+
+
     def test_add_class_without_valid_args(self):
         class NoValidArgs:
             def __init__(self, a0=None):
diff --git a/jsonargparse_tests/test_typehints.py b/jsonargparse_tests/test_typehints.py
index d9cf945..e2d3230 100755
--- a/jsonargparse_tests/test_typehints.py
+++ b/jsonargparse_tests/test_typehints.py
@@ -588,6 +588,21 @@ class TypeHintsTests(unittest.TestCase):
             self.assertIn(f'{module}.HTMLCalendar', err.getvalue())
 
 
+    def test_class_type_set_defaults_class_name(self):
+        parser = ArgumentParser()
+        parser.add_argument('--cal', type=Calendar)
+        parser.set_defaults({
+            'cal': {
+                'class_path': 'TextCalendar',
+                'init_args': {
+                    'firstweekday': 1,
+                }
+            }
+        })
+        cal = parser.get_default('cal').as_dict()
+        self.assertEqual(cal, {'class_path': 'calendar.TextCalendar', 'init_args': {'firstweekday': 1}})
+
+
     def test_class_type_subclass_short_init_args(self):
         parser = ArgumentParser()
         parser.add_argument('--op', type=Calendar)
@@ -1084,6 +1099,32 @@ class TypeHintsTmpdirTests(TempDirTestCase):
         self.assertEqual(cfg.data.cal, Namespace(class_path='calendar.Calendar'))
 
 
+    def test_class_path_override_config_with_defaults(self):
+        class Base:
+            def __init__(self, b: int = 1):
+                pass
+
+        class Subclass1(Base):
+            def __init__(self, s1: str = '-'):
+                pass
+
+        class Subclass2(Base):
+            def __init__(self, s2: str = '-'):
+                pass
+
+        with mock_module(Base, Subclass1, Subclass2) as module:
+            parser = ArgumentParser()
+            parser.add_argument('--cfg', action=ActionConfigFile)
+            parser.add_argument('--s', type=Base, default=lazy_instance(Subclass1, s1='v1'))
+
+            config = {'s': {'class_path': 'Subclass2', 'init_args': {'s2': 'v2'}}}
+            with warnings.catch_warnings(record=True) as w:
+                cfg = parser.parse_args([f'--cfg={config}'])
+                self.assertIn("discarding init_args: {'s1': 'v1'}", str(w[0].message))
+            self.assertEqual(cfg.s.class_path, f'{module}.Subclass2')
+            self.assertEqual(cfg.s.init_args, Namespace(s2='v2'))
+
+
     def test_class_path_override_with_default_config_files(self):
 
         class MyCalendar(Calendar):