Repository: omni-us/jsonargparse
Pull request #490 (2024-04-16 05:17:50)

Pending changes from pull request #475
## What does this PR do?

- Only raise exception for "-" value when failure is from Path.
- Reintroduce caching of stdin needed for path lists.

## Before submitting

- [x] Did you read the [contributing guideline](https://github.com/omni-us/jsonargparse/blob/main/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]: ## [![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=490) **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=490&resolved=false&inNewCodePeriod=true)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/accepted-16px.png '') [0 Accepted issues](https://sonarcloud.io/component_measures?id=omni-us_jsonargparse&pullRequest=490&metric=new_accepted_issues&view=list)

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=490&resolved=false&inNewCodePeriod=true)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/no-data-16px.png '') No data about Coverage  
![](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=490&metric=new_duplicated_lines_density&view=list)  
  
[See analysis details on SonarCloud](https://sonarcloud.io/dashboard?id=omni-us_jsonargparse&pullRequest=490)


codecov[bot]: ## [Codecov](https://app.codecov.io/gh/omni-us/jsonargparse/pull/490?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.80%. Comparing base [(`9906794`)](https://app.codecov.io/gh/omni-us/jsonargparse/commit/99067949ac82d795bf591c1c83e0fda129c882d9?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=omni-us) to head [(`0108fae`)](https://app.codecov.io/gh/omni-us/jsonargparse/pull/490?dropdown=coverage&src=pr&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     #490      +/-   ##
===========================================
- Coverage   100.00%   99.80%   -0.20%     
===========================================
  Files           21       21              
  Lines         6027     6039      +12     
===========================================
  Hits          6027     6027              
- Misses           0       12      +12     
```

| [Flag](https://app.codecov.io/gh/omni-us/jsonargparse/pull/490/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/490/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/490/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/490/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=omni-us) | `?` | |
| [py3.10_pydantic2](https://app.codecov.io/gh/omni-us/jsonargparse/pull/490/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=omni-us) | `?` | |
| [py3.10_types](https://app.codecov.io/gh/omni-us/jsonargparse/pull/490/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/490/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=omni-us) | `87.63% <91.42%> (+0.02%)` | :arrow_up: |
| [py3.11_all](https://app.codecov.io/gh/omni-us/jsonargparse/pull/490/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=omni-us) | `98.72% <100.00%> (+<0.01%)` | :arrow_up: |
| [py3.11_types](https://app.codecov.io/gh/omni-us/jsonargparse/pull/490/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=omni-us) | `98.72% <100.00%> (+<0.01%)` | :arrow_up: |
| [py3.12](https://app.codecov.io/gh/omni-us/jsonargparse/pull/490/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=omni-us) | `87.63% <91.42%> (+0.02%)` | :arrow_up: |
| [py3.12_all](https://app.codecov.io/gh/omni-us/jsonargparse/pull/490/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=omni-us) | `98.72% <100.00%> (+<0.01%)` | :arrow_up: |
| [py3.12_types](https://app.codecov.io/gh/omni-us/jsonargparse/pull/490/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=omni-us) | `98.72% <100.00%> (+<0.01%)` | :arrow_up: |
| [py3.7](https://app.codecov.io/gh/omni-us/jsonargparse/pull/490/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=omni-us) | `88.20% <91.42%> (+0.02%)` | :arrow_up: |
| [py3.7_all](https://app.codecov.io/gh/omni-us/jsonargparse/pull/490/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=omni-us) | `99.48% <100.00%> (+<0.01%)` | :arrow_up: |
| [py3.7_types](https://app.codecov.io/gh/omni-us/jsonargparse/pull/490/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=omni-us) | `99.46% <100.00%> (+<0.01%)` | :arrow_up: |
| [py3.8](https://app.codecov.io/gh/omni-us/jsonargparse/pull/490/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=omni-us) | `88.35% <91.42%> (+0.02%)` | :arrow_up: |
| [py3.8_all](https://app.codecov.io/gh/omni-us/jsonargparse/pull/490/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=omni-us) | `99.50% <100.00%> (+<0.01%)` | :arrow_up: |
| [py3.8_types](https://app.codecov.io/gh/omni-us/jsonargparse/pull/490/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=omni-us) | `99.48% <100.00%> (+<0.01%)` | :arrow_up: |
| [py3.9](https://app.codecov.io/gh/omni-us/jsonargparse/pull/490/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=omni-us) | `88.20% <91.42%> (+0.02%)` | :arrow_up: |
| [py3.9_all](https://app.codecov.io/gh/omni-us/jsonargparse/pull/490/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=omni-us) | `99.37% <100.00%> (+<0.01%)` | :arrow_up: |
| [py3.9_types](https://app.codecov.io/gh/omni-us/jsonargparse/pull/490/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=omni-us) | `99.37% <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.


</details>

[:umbrella: View full report in Codecov by Sentry](https://app.codecov.io/gh/omni-us/jsonargparse/pull/490?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 1d0d3e6..e3bc0ed 100644
--- a/CHANGELOG.rst
+++ b/CHANGELOG.rst
@@ -11,6 +11,23 @@ The semantic versioning only considers the public API as described in
 :ref:`api-ref`. Components not mentioned in :ref:`api-ref` or different import
 paths are considered internals and can change in minor and patch releases.
 
+v4.28.1 (2024-0?-??)
+--------------------
+
+Added
+^^^^^
+- `TypeAliasType` support added (`#480
+  <https://github.com/omni-us/jsonargparse/issues/480>`__).
+
+Fixed
+^^^^^
+- Attrs and Pydantic 2 dataclasses with non-init fields fail to instantiate
+  (`#480 <https://github.com/omni-us/jsonargparse/issues/480>`__).
+- Default values/factories for Pydantic 2 dataclasses with `Field` initializers
+  are not right (`#480 <https://github.com/omni-us/jsonargparse/issues/480>`__).
+- `Annotated` fields in dataclass-likes (eg FastAPI types) resolve incorrectly
+  (`#480 <https://github.com/omni-us/jsonargparse/issues/480>`__).
+
 
 v4.28.0 (2024-03-??)
 --------------------
@@ -18,7 +35,8 @@ v4.28.0 (2024-03-??)
 Added
 ^^^^^
 - Support for "-" as value for Path class initialization so that user
-  can ask to use standard input/output instead of file.
+  can ask to use standard input/output instead of file (`#463
+  <https://github.com/omni-us/jsonargparse/issues/463>`__).
 - Option in ``CLI`` to provide subcommands help when components given in a dict
   (`litgpt#996 comment
   <https://github.com/Lightning-AI/litgpt/issues/996#issuecomment-1996201548>`__).
diff --git a/DOCUMENTATION.rst b/DOCUMENTATION.rst
index 950bdda..2e053da 100644
--- a/DOCUMENTATION.rst
+++ b/DOCUMENTATION.rst
@@ -447,6 +447,9 @@ Some notes about this support are:
   see :ref:`callable-type`. Currently the callable's argument and return types
   are not validated.
 
+- ``TypeAliasType`` is supported with values parsed as the aliased type and the
+  alias shown as the argument type in help.
+
 
 .. _restricted-numbers:
 
diff --git a/jsonargparse/_common.py b/jsonargparse/_common.py
index 4649485..9a5b2b0 100644
--- a/jsonargparse/_common.py
+++ b/jsonargparse/_common.py
@@ -19,7 +19,14 @@ from typing import (  # type: ignore[attr-defined]
 )
 
 from ._namespace import Namespace
-from ._optionals import import_reconplogger, reconplogger_support
+from ._optionals import (
+    get_alias_target,
+    get_annotated_base_type,
+    import_reconplogger,
+    is_alias_type,
+    is_annotated,
+    reconplogger_support,
+)
 from ._type_checking import ArgumentParser
 
 __all__ = [
@@ -99,6 +106,19 @@ def get_generic_origin(cls):
     return cls.__origin__ if is_generic_class(cls) else cls
 
 
+def get_unaliased_type(cls):
+    new_cls = cls
+    while True:
+        cur_cls = new_cls
+        if is_annotated(new_cls):
+            new_cls = get_annotated_base_type(new_cls)
+        if is_alias_type(new_cls):
+            new_cls = get_alias_target(new_cls)
+        if new_cls == cur_cls:
+            break
+    return cur_cls
+
+
 def is_dataclass_like(cls) -> bool:
     if is_generic_class(cls):
         return is_dataclass_like(cls.__origin__)
diff --git a/jsonargparse/_optionals.py b/jsonargparse/_optionals.py
index d540c8c..32f7f1f 100644
--- a/jsonargparse/_optionals.py
+++ b/jsonargparse/_optionals.py
@@ -37,7 +37,7 @@ _docstring_parse_options = {
 
 def typing_extensions_import(name):
     if typing_extensions_support:
-        return getattr(__import__("typing_extensions"), name)
+        return getattr(__import__("typing_extensions"), name, False)
     else:
         return getattr(__import__("typing"), name, False)
 
@@ -313,6 +313,21 @@ def is_annotated(typehint: type) -> bool:
     return annotated_alias and isinstance(typehint, annotated_alias)
 
 
+def get_annotated_base_type(typehint: type) -> type:
+    return typehint.__origin__  # type: ignore[attr-defined]
+
+
+type_alias_type = typing_extensions_import("TypeAliasType")
+
+
+def is_alias_type(typehint: type) -> bool:
+    return type_alias_type and isinstance(typehint, type_alias_type)
+
+
+def get_alias_target(typehint: type) -> bool:
+    return typehint.__value__  # type: ignore[attr-defined]
+
+
 def get_pydantic_support() -> int:
     support = "0"
     if find_spec("pydantic"):
@@ -331,6 +346,24 @@ def get_pydantic_support() -> int:
 pydantic_support = get_pydantic_support()
 
 
+def get_pydantic_supports_field_init() -> bool:
+    if find_spec("pydantic"):
+        try:
+            from importlib.metadata import version
+
+            support = version("pydantic")
+        except ImportError:
+            import pydantic
+
+            support = pydantic.version.VERSION
+        major, minor = tuple(int(x) for x in support.split(".")[:2])
+        return major > 2 or (major == 2 and minor >= 6)
+    return False
+
+
+pydantic_supports_field_init = get_pydantic_supports_field_init()
+
+
 def is_pydantic_model(class_type) -> int:
     classes = inspect.getmro(class_type) if pydantic_support and inspect.isclass(class_type) else []
     for cls in classes:
diff --git a/jsonargparse/_parameter_resolvers.py b/jsonargparse/_parameter_resolvers.py
index f0f1f85..7ea976a 100644
--- a/jsonargparse/_parameter_resolvers.py
+++ b/jsonargparse/_parameter_resolvers.py
@@ -13,8 +13,16 @@ from importlib import import_module
 from types import MethodType
 from typing import Any, Callable, Dict, List, Optional, Tuple, Type, Union
 
-from ._common import LoggerProperty, get_generic_origin, is_dataclass_like, is_generic_class, is_subclass, parse_logger
-from ._optionals import is_pydantic_model, parse_docs
+from ._common import (
+    LoggerProperty,
+    get_generic_origin,
+    get_unaliased_type,
+    is_dataclass_like,
+    is_generic_class,
+    is_subclass,
+    parse_logger,
+)
+from ._optionals import get_annotated_base_type, is_annotated, is_pydantic_model, parse_docs
 from ._postponed_annotations import evaluate_postponed_annotations
 from ._stubs_resolver import get_stub_types
 from ._util import (
@@ -861,9 +869,32 @@ def get_field_data_pydantic1_model(field, name, doc_params):
 
 
 def get_field_data_pydantic2_dataclass(field, name, doc_params):
+    from pydantic.fields import FieldInfo
+    from pydantic_core import PydanticUndefined
+
+    default = inspect._empty
+    # Identify the default.
+    if isinstance(field.default, FieldInfo):
+        # Pydantic 2 dataclasses stuff their FieldInfo into a
+        # stdlib dataclasses.field's `default`; this is where the
+        # actual default and default_factory live.
+        if field.default.default is not PydanticUndefined:
+            default = field.default.default
+        elif field.default.default_factory is not PydanticUndefined:
+            default = field.default.default_factory()
+    elif field.default is not dataclasses.MISSING:
+        default = field.default
+    elif field.default_factory is not dataclasses.MISSING:
+        default = field.default_factory()
+
+    # Get the type, stripping Annotated like get_type_hints does.
+    if is_annotated(field.type):
+        field_type = get_annotated_base_type(field.type)
+    else:
+        field_type = field.type
     return dict(
-        annotation=field.type,
-        default=field.default,
+        annotation=field_type,
+        default=default,
         doc=doc_params.get(name),
     )
 
@@ -898,6 +929,19 @@ def get_field_data_attrs(field, name, doc_params):
     )
 
 
+def is_init_field_pydantic2_dataclass(field) -> bool:
+    from pydantic.fields import FieldInfo
+
+    if isinstance(field.default, FieldInfo):
+        # FieldInfo.init is new in pydantic 2.6
+        return getattr(field.default, "init", None) is not False
+    return field.init is not False
+
+
+def is_init_field_attrs(field) -> bool:
+    return field.init is not False
+
+
 def get_parameters_from_pydantic_or_attrs(
     function_or_class: Union[Callable, Type],
     method_or_property: Optional[str],
@@ -907,20 +951,23 @@ def get_parameters_from_pydantic_or_attrs(
 
     if method_or_property or not (pydantic_support or attrs_support):
         return None
-
+    function_or_class = get_unaliased_type(function_or_class)
     fields_iterator = get_field_data = None
     if pydantic_support:
         pydantic_model = is_pydantic_model(function_or_class)
         if pydantic_model == 1:
-            fields_iterator = function_or_class.__fields__.items()  # type: ignore[union-attr]
+            fields_iterator = function_or_class.__fields__.items()
             get_field_data = get_field_data_pydantic1_model
+            is_init_field = lambda _: True
         elif pydantic_model > 1:
-            fields_iterator = function_or_class.model_fields.items()  # type: ignore[union-attr]
+            fields_iterator = function_or_class.model_fields.items()
             get_field_data = get_field_data_pydantic2_model
+            is_init_field = lambda _: True
         elif dataclasses.is_dataclass(function_or_class) and hasattr(function_or_class, "__pydantic_fields__"):
             fields_iterator = dataclasses.fields(function_or_class)
             fields_iterator = {v.name: v for v in fields_iterator}.items()
             get_field_data = get_field_data_pydantic2_dataclass
+            is_init_field = is_init_field_pydantic2_dataclass
 
     if not fields_iterator and attrs_support:
         import attrs
@@ -928,6 +975,7 @@ def get_parameters_from_pydantic_or_attrs(
         if attrs.has(function_or_class):
             fields_iterator = {f.name: f for f in attrs.fields(function_or_class)}.items()
             get_field_data = get_field_data_attrs
+            is_init_field = is_init_field_attrs
 
     if not fields_iterator or not get_field_data:
         return None
@@ -935,14 +983,15 @@ def get_parameters_from_pydantic_or_attrs(
     params = []
     doc_params = parse_docs(function_or_class, None, logger)
     for name, field in fields_iterator:
-        params.append(
-            ParamData(
-                name=name,
-                kind=kinds.KEYWORD_ONLY,
-                component=function_or_class,
-                **get_field_data(field, name, doc_params),
+        if is_init_field(field):
+            params.append(
+                ParamData(
+                    name=name,
+                    kind=kinds.KEYWORD_ONLY,
+                    component=function_or_class,
+                    **get_field_data(field, name, doc_params),
+                )
             )
-        )
     evaluate_postponed_annotations(params, function_or_class, None, logger)
     return params
 
diff --git a/jsonargparse/_signatures.py b/jsonargparse/_signatures.py
index 94ca7af..8a40804 100644
--- a/jsonargparse/_signatures.py
+++ b/jsonargparse/_signatures.py
@@ -8,7 +8,14 @@ from contextlib import suppress
 from typing import Any, Callable, List, Optional, Set, Tuple, Type, Union
 
 from ._actions import _ActionConfigLoad
-from ._common import LoggerProperty, get_class_instantiator, get_generic_origin, is_dataclass_like, is_subclass
+from ._common import (
+    LoggerProperty,
+    get_class_instantiator,
+    get_generic_origin,
+    get_unaliased_type,
+    is_dataclass_like,
+    is_subclass,
+)
 from ._optionals import get_doc_short_description, is_pydantic_model, pydantic_support
 from ._parameter_resolvers import (
     ParamData,
@@ -72,7 +79,7 @@ class SignatureArguments(LoggerProperty):
             ValueError: When not given a class.
             ValueError: When there are required parameters without at least one valid type.
         """
-        if not inspect.isclass(get_generic_origin(theclass)):
+        if not inspect.isclass(get_generic_origin(get_unaliased_type(theclass))):
             raise ValueError(f'Expected "theclass" parameter to be a class type, got: {theclass}.')
         if default and not (isinstance(default, LazyInitBaseClass) and isinstance(default, theclass)):
             raise ValueError(f'Expected "default" parameter to be a lazy instance of the class, got: {default}.')
@@ -133,9 +140,10 @@ class SignatureArguments(LoggerProperty):
             ValueError: When not given a class or the name of a method of the class.
             ValueError: When there are required parameters without at least one valid type.
         """
-        if not inspect.isclass(get_generic_origin(theclass)):
+        unaliased_type = get_unaliased_type(theclass)
+        if not inspect.isclass(get_generic_origin(unaliased_type)):
             raise ValueError('Expected "theclass" argument to be a class object.')
-        if not hasattr(theclass, themethod) or not callable(getattr(theclass, themethod)):
+        if not hasattr(unaliased_type, themethod) or not callable(getattr(unaliased_type, themethod)):
             raise ValueError('Expected "themethod" argument to be a callable member of the class.')
 
         return self._add_signature_arguments(
@@ -440,7 +448,7 @@ class SignatureArguments(LoggerProperty):
             if isinstance(default, dict):
                 with suppress(TypeError):
                     default = theclass(**default)
-            if not isinstance(default, theclass):
+            if not isinstance(default, get_unaliased_type(theclass)):
                 raise ValueError(
                     f'Expected "default" argument to be an instance of "{theclass.__name__}" '
                     f"or its kwargs dict, given {default}"
diff --git a/jsonargparse/_typehints.py b/jsonargparse/_typehints.py
index eeceb59..7fe36f9 100644
--- a/jsonargparse/_typehints.py
+++ b/jsonargparse/_typehints.py
@@ -43,6 +43,7 @@ from ._actions import (
 )
 from ._common import (
     get_class_instantiator,
+    get_unaliased_type,
     is_dataclass_like,
     is_subclass,
     nested_links,
@@ -58,7 +59,9 @@ from ._loaders_dumpers import (
 from ._namespace import Namespace
 from ._optionals import (
     argcomplete_warn_redraw_prompt,
+    get_alias_target,
     get_files_completer,
+    is_alias_type,
     is_annotated,
     is_annotated_validator,
     typing_extensions_import,
@@ -69,6 +72,7 @@ from ._util import (
     NestedArg,
     NoneType,
     Path,
+    PathError,
     change_to_path_dir,
     get_import_path,
     get_typehint_origin,
@@ -234,8 +238,8 @@ class ActionTypeHint(Action):
     @staticmethod
     def is_supported_typehint(typehint, full=False):
         """Whether the given type hint is supported."""
-        if is_annotated(typehint):
-            typehint = get_typehint_origin(typehint)
+        typehint = get_unaliased_type(typehint)
+
         supported = (
             typehint in root_types
             or get_typehint_origin(typehint) in root_types
@@ -269,6 +273,7 @@ class ActionTypeHint(Action):
         typehint = typehint_from_action(typehint)
         if typehint is None:
             return False
+        typehint = get_unaliased_type(typehint)
         typehint_origin = get_typehint_origin(typehint)
         if typehint_origin == Union or (also_lists and typehint_origin in sequence_origin_types):
             subtypes = [a for a in typehint.__args__ if a != NoneType]
@@ -287,7 +292,7 @@ class ActionTypeHint(Action):
 
     @staticmethod
     def is_return_subclass_typehint(typehint):
-        typehint = get_optional_arg(typehint)
+        typehint = get_unaliased_type(get_optional_arg(get_unaliased_type(typehint)))
         typehint_origin = get_typehint_origin(typehint)
         if typehint_origin in callable_origin_types:
             return_type = get_callable_return_type(typehint)
@@ -297,6 +302,7 @@ class ActionTypeHint(Action):
 
     @staticmethod
     def is_mapping_typehint(typehint):
+        typehint = get_unaliased_type(typehint)
         typehint_origin = get_typehint_origin(typehint) or typehint
         if (
             typehint in mapping_origin_types
@@ -308,6 +314,7 @@ class ActionTypeHint(Action):
 
     @staticmethod
     def is_callable_typehint(typehint, all_subtypes=True):
+        typehint = get_unaliased_type(typehint)
         typehint_origin = get_typehint_origin(typehint)
         if typehint_origin == Union:
             subtypes = [a for a in typehint.__args__ if a != NoneType]
@@ -510,7 +517,7 @@ class ActionTypeHint(Action):
                         val = adapt_typehints(val, self._typehint, **kwargs)
                 except ValueError as ex:
                     assert ex  # needed due to ruff bug that removes " as ex"
-                    if orig_val == "-":
+                    if orig_val == "-" and isinstance(getattr(ex, "parent", None), PathError):
                         raise ex
                     try:
                         if isinstance(orig_val, str):
@@ -918,6 +925,10 @@ def adapt_typehints(
             error = indent_text(str(ex))
             raise_unexpected_value(f"Problem with given class_path {class_path!r}:\n{error}", exception=ex)
 
+    # TypeAliasType -- 3.12 `type x = y` or manually via typing_extensions
+    elif is_alias_type(typehint):
+        return adapt_typehints(val, get_alias_target(typehint), **adapt_kwargs)
+
     return val
 
 
diff --git a/jsonargparse/_util.py b/jsonargparse/_util.py
index 726117f..6e1ab6b 100644
--- a/jsonargparse/_util.py
+++ b/jsonargparse/_util.py
@@ -156,6 +156,19 @@ class CachedStdin(StringIO):
     """Used to allow reading sys.stdin multiple times."""
 
 
+def get_cached_stdin() -> CachedStdin:
+    if not isinstance(sys.stdin, CachedStdin):
+        sys.stdin = CachedStdin(sys.stdin.read())
+    return sys.stdin
+
+
+def read_cached_stdin() -> str:
+    stdin = get_cached_stdin()
+    value = stdin.read()
+    stdin.seek(0)
+    return value
+
+
 def import_object(name: str):
     """Returns an object in a module given its dot import path."""
     if not isinstance(name, str) or "." not in name:
@@ -434,6 +447,10 @@ def resolve_relative_path(path: str) -> str:
     return "/".join(resolved)
 
 
+class PathError(TypeError):
+    """Exception raised for errors in the Path class."""
+
+
 class Path(PathDeprecations):
     """Stores a (possibly relative) path and the corresponding absolute path.
 
@@ -475,7 +492,7 @@ class Path(PathDeprecations):
 
         Raises:
             ValueError: If the provided mode is invalid.
-            TypeError: If the path does not exist or does not agree with the mode.
+            PathError: If the path does not exist or does not agree with the mode.
         """
         self._deprecated_kwargs(kwargs)
         self._check_mode(mode)
@@ -519,7 +536,7 @@ class Path(PathDeprecations):
                 abs_path = abs_path if is_absolute else os.path.join(cwd, abs_path)
                 url_data = None
         else:
-            raise TypeError("Expected path to be a string, os.PathLike or a Path object.")
+            raise PathError("Expected path to be a string, os.PathLike or a Path object.")
 
         if not self._skip_check and is_url:
             if "r" in mode:
@@ -527,7 +544,7 @@ class Path(PathDeprecations):
                 try:
                     requests.head(abs_path).raise_for_status()
                 except requests.HTTPError as ex:
-                    raise TypeError(f"{abs_path} HEAD not accessible :: {ex}") from ex
+                    raise PathError(f"{abs_path} HEAD not accessible :: {ex}") from ex
         elif not self._skip_check and is_fsspec:
             fsspec_mode = "".join(c for c in mode if c in {"r", "w"})
             if fsspec_mode:
@@ -537,9 +554,9 @@ class Path(PathDeprecations):
                     handle.open()
                     handle.close()
                 except (FileNotFoundError, KeyError) as ex:
-                    raise TypeError(f"Path does not exist: {abs_path!r}") from ex
+                    raise PathError(f"Path does not exist: {abs_path!r}") from ex
                 except PermissionError as ex:
-                    raise TypeError(f"Path exists but no permission to access: {abs_path!r}") from ex
+                    raise PathError(f"Path exists but no permission to access: {abs_path!r}") from ex
         elif not self._skip_check and not self._std_io:
             ptype = "Directory" if "d" in mode else "File"
             if "c" in mode:
@@ -550,37 +567,37 @@ class Path(PathDeprecations):
                         ppdir = pdir
                         pdir = os.path.realpath(os.path.join(pdir, ".."))
                 if not os.path.isdir(pdir):
-                    raise TypeError(f"{ptype} is not creatable since parent directory does not exist: {abs_path!r}")
+                    raise PathError(f"{ptype} is not creatable since parent directory does not exist: {abs_path!r}")
                 if not os.access(pdir, os.W_OK):
-                    raise TypeError(f"{ptype} is not creatable since parent directory not writeable: {abs_path!r}")
+                    raise PathError(f"{ptype} is not creatable since parent directory not writeable: {abs_path!r}")
                 if "d" in mode and os.access(abs_path, os.F_OK) and not os.path.isdir(abs_path):
-                    raise TypeError(f"{ptype} is not creatable since path already exists: {abs_path!r}")
+                    raise PathError(f"{ptype} is not creatable since path already exists: {abs_path!r}")
                 if "f" in mode and os.access(abs_path, os.F_OK) and not os.path.isfile(abs_path):
-                    raise TypeError(f"{ptype} is not creatable since path already exists: {abs_path!r}")
+                    raise PathError(f"{ptype} is not creatable since path already exists: {abs_path!r}")
             elif "d" in mode or "f" in mode:
                 if not os.access(abs_path, os.F_OK):
-                    raise TypeError(f"{ptype} does not exist: {abs_path!r}")
+                    raise PathError(f"{ptype} does not exist: {abs_path!r}")
                 if "d" in mode and not os.path.isdir(abs_path):
-                    raise TypeError(f"Path is not a directory: {abs_path!r}")
+                    raise PathError(f"Path is not a directory: {abs_path!r}")
                 if "f" in mode and not (os.path.isfile(abs_path) or stat.S_ISFIFO(os.stat(abs_path).st_mode)):
-                    raise TypeError(f"Path is not a file: {abs_path!r}")
+                    raise PathError(f"Path is not a file: {abs_path!r}")
 
             if "r" in mode and not os.access(abs_path, os.R_OK):
-                raise TypeError(f"{ptype} is not readable: {abs_path!r}")
+                raise PathError(f"{ptype} is not readable: {abs_path!r}")
             if "w" in mode and not os.access(abs_path, os.W_OK):
-                raise TypeError(f"{ptype} is not writeable: {abs_path!r}")
+                raise PathError(f"{ptype} is not writeable: {abs_path!r}")
             if "x" in mode and not os.access(abs_path, os.X_OK):
-                raise TypeError(f"{ptype} is not executable: {abs_path!r}")
+                raise PathError(f"{ptype} is not executable: {abs_path!r}")
             if "D" in mode and os.path.isdir(abs_path):
-                raise TypeError(f"Path is a directory: {abs_path!r}")
+                raise PathError(f"Path is a directory: {abs_path!r}")
             if "F" in mode and (os.path.isfile(abs_path) or stat.S_ISFIFO(os.stat(abs_path).st_mode)):
-                raise TypeError(f"Path is a file: {abs_path!r}")
+                raise PathError(f"Path is a file: {abs_path!r}")
             if "R" in mode and os.access(abs_path, os.R_OK):
-                raise TypeError(f"{ptype} is readable: {abs_path!r}")
+                raise PathError(f"{ptype} is readable: {abs_path!r}")
             if "W" in mode and os.access(abs_path, os.W_OK):
-                raise TypeError(f"{ptype} is writeable: {abs_path!r}")
+                raise PathError(f"{ptype} is writeable: {abs_path!r}")
             if "X" in mode and os.access(abs_path, os.X_OK):
-                raise TypeError(f"{ptype} is executable: {abs_path!r}")
+                raise PathError(f"{ptype} is executable: {abs_path!r}")
 
         self._relative = path
         self._absolute = abs_path
@@ -644,7 +661,7 @@ class Path(PathDeprecations):
     def get_content(self, mode: str = "r") -> str:
         """Returns the contents of the file or the remote path."""
         if self._std_io:
-            return sys.stdin.read()
+            return read_cached_stdin()
         elif self._is_url:
             assert mode == "r"
             requests = import_requests("Path.get_content")
@@ -665,7 +682,7 @@ class Path(PathDeprecations):
         """Return an opened file object for the path."""
         if self._std_io:
             if "r" in mode:
-                yield sys.stdin
+                yield get_cached_stdin()
             elif "w" in mode:
                 yield sys.stdout
         elif self._is_url:
diff --git a/jsonargparse/typing.py b/jsonargparse/typing.py
index f252d09..aea7716 100644
--- a/jsonargparse/typing.py
+++ b/jsonargparse/typing.py
@@ -280,7 +280,9 @@ class RegisteredType:
             return self.base_deserializer(value)
         except self.deserializer_exceptions as ex:
             type_class_name = getattr(self.type_class, "__name__", str(self.type_class))
-            raise ValueError(f"Not of type {type_class_name}: {ex}") from ex
+            ex2 = ValueError(f"Not of type {type_class_name}: {ex}")
+            ex2.parent = ex
+            raise ex2 from ex
 
 
 def register_type(


Test patch:
diff --git a/jsonargparse_tests/conftest.py b/jsonargparse_tests/conftest.py
index 6dc8238..faf0750 100644
--- a/jsonargparse_tests/conftest.py
+++ b/jsonargparse_tests/conftest.py
@@ -7,7 +7,7 @@ from importlib.util import find_spec
 from io import StringIO
 from pathlib import Path
 from typing import Iterator, List
-from unittest.mock import patch
+from unittest.mock import MagicMock, patch
 
 import pytest
 
@@ -113,6 +113,18 @@ def file_r(tmp_cwd) -> Iterator[str]:
     yield filename
 
 
+@pytest.fixture
+def mock_stdin():
+    @contextmanager
+    def _mock_stdin(data: str):
+        mock = MagicMock()
+        mock.read.side_effect = [data, ""]
+        with patch("sys.stdin", mock):
+            yield
+
+    return _mock_stdin
+
+
 @pytest.fixture
 def logger() -> logging.Logger:
     logger = logging.getLogger(__name__)
diff --git a/jsonargparse_tests/test_dataclass_like.py b/jsonargparse_tests/test_dataclass_like.py
index 0402eee..765d1bf 100644
--- a/jsonargparse_tests/test_dataclass_like.py
+++ b/jsonargparse_tests/test_dataclass_like.py
@@ -19,6 +19,7 @@ from jsonargparse._optionals import (
     attrs_support,
     docstring_parser_support,
     pydantic_support,
+    pydantic_supports_field_init,
     set_docstring_parse_options,
     typing_extensions_import,
 )
@@ -28,6 +29,9 @@ from jsonargparse_tests.conftest import (
     skip_if_docstring_parser_unavailable,
 )
 
+annotated = typing_extensions_import("Annotated")
+type_alias_type = typing_extensions_import("TypeAliasType")
+
 # dataclass tests
 
 
@@ -208,14 +212,15 @@ def test_add_argument_dataclass_type_required_attr(parser):
 
 @dataclasses.dataclass
 class DataInitFalse:
-    p1: str = "-"
-    p2: str = dataclasses.field(init=False)
+    p1: str = dataclasses.field(init=False)
 
 
 def test_dataclass_field_init_false(parser):
     added = parser.add_dataclass_arguments(DataInitFalse, "d")
-    assert added == ["d.p1"]
-    assert parser.get_defaults() == Namespace(d=Namespace(p1="-"))
+    assert added == []
+    assert parser.get_defaults() == Namespace()
+    cfg = parser.parse_args(["--d", "{}"])
+    assert cfg.d == Namespace()
 
 
 @dataclasses.dataclass
@@ -468,9 +473,108 @@ def test_add_class_final(parser):
     pytest.raises(ValueError, lambda: parser.add_class_arguments(FinalClass, "a", default=FinalClass()))
 
 
+if type_alias_type:
+    IntOrString = type_alias_type("IntOrString", Union[int, str])
+
+    @dataclasses.dataclass
+    class DataClassWithAliasType:
+        p1: IntOrString  # type: ignore[valid-type]
+
+    def test_bare_alias_type(parser):
+        parser.add_argument("--data", type=IntOrString)
+        help_str = get_parser_help(parser)
+        help_str_lines = [line for line in help_str.split("\n") if "type: IntOrString" in line]
+        assert len(help_str_lines) == 1
+        assert "--data DATA" in help_str_lines[0]
+        cfg = parser.parse_args(["--data=MyString"])
+        assert cfg.data == "MyString"
+        cfg = parser.parse_args(["--data=3"])
+        assert cfg.data == 3
+
+    def test_dataclass_with_alias_type(parser):
+        parser.add_argument("--data", type=DataClassWithAliasType)
+        help_str = get_parser_help(parser)
+        help_str_lines = [line for line in help_str.split("\n") if "type: IntOrString" in line]
+        assert len(help_str_lines) == 1
+        assert "--data.p1 P1" in help_str_lines[0]
+        cfg = parser.parse_args(["--data.p1=MyString"])
+        assert cfg.data.p1 == "MyString"
+        cfg = parser.parse_args(["--data.p1=3"])
+        assert cfg.data.p1 == 3
+
+    @pytest.mark.skipif(not annotated, reason="Annotated is required")
+    def test_annotated_alias_type(parser):
+        parser.add_argument("--data", type=annotated[IntOrString, 1])
+        help_str = get_parser_help(parser)
+        help_str_lines = [line for line in help_str.split("\n") if "type: Annotated[IntOrString, 1]" in line]
+        assert len(help_str_lines) == 1
+        assert "--data DATA" in help_str_lines[0]
+        cfg = parser.parse_args(["--data=MyString"])
+        assert cfg.data == "MyString"
+        cfg = parser.parse_args(["--data=3"])
+        assert cfg.data == 3
+
+    if annotated:
+
+        @dataclasses.dataclass
+        class DataClassWithAnnotatedAliasType:
+            p1: annotated[IntOrString, 1]  # type: ignore[valid-type]
+
+    @pytest.mark.skipif(not annotated, reason="Annotated is required")
+    def test_dataclass_with_annotated_alias_type(parser):
+        parser.add_argument("--data", type=DataClassWithAnnotatedAliasType)
+        help_str = get_parser_help(parser)
+        # The printable field datatype is not uniform across versions.
+        help_str_lines = [line for line in help_str.split("\n") if "type:" in line and "IntOrString" in line]
+        assert len(help_str_lines) == 1
+        assert "--data.p1 P1" in help_str_lines[0]
+        cfg = parser.parse_args(["--data.p1=MyString"])
+        assert cfg.data.p1 == "MyString"
+        cfg = parser.parse_args(["--data.p1=3"])
+        assert cfg.data.p1 == 3
+
+
 # pydantic tests
+if annotated and pydantic_support > 1:
+    import pydantic
+
+    @pydantic.dataclasses.dataclass(frozen=True)
+    class InnerDataClass:
+        a2: int = 1
+
+    @pydantic.dataclasses.dataclass(frozen=True)
+    class NestedAnnotatedDataClass:
+        a1: annotated[InnerDataClass, 1]  # type: ignore[valid-type]
+
+    @pydantic.dataclasses.dataclass(frozen=True)
+    class NestedAnnotatedDataClassWithDefault:
+        a1: annotated[InnerDataClass, 1] = pydantic.fields.Field(default=InnerDataClass())  # type: ignore[valid-type]
+
+    @pydantic.dataclasses.dataclass(frozen=True)
+    class NestedAnnotatedDataClassWithDefaultFactory:
+        a1: annotated[InnerDataClass, 1] = pydantic.fields.Field(default_factory=InnerDataClass)  # type: ignore[valid-type]
+
+    def test_pydantic_nested_annotated_dataclass(parser: ArgumentParser):
+        parser.add_class_arguments(NestedAnnotatedDataClass, "n")
+        cfg = parser.parse_args(["--n", "{}"])
+        assert cfg.n == Namespace(a1=Namespace(a2=1))
+
+    def test_pydantic_annotated_nested_annotated_dataclass(parser: ArgumentParser):
+        parser.add_class_arguments(annotated[NestedAnnotatedDataClass, 1], "n")
+        cfg = parser.parse_args(["--n", "{}"])
+        assert cfg.n == Namespace(a1=Namespace(a2=1))
+
+    def test_pydantic_annotated_nested_annotated_dataclass_with_default(parser: ArgumentParser):
+        parser.add_class_arguments(annotated[NestedAnnotatedDataClassWithDefault, 1], "n")
+        cfg = parser.parse_args(["--n", "{}"])
+        assert cfg.n == Namespace(a1=Namespace(a2=1))
+
+    def test_pydantic_annotated_nested_annotated_dataclass_with_default_factory(parser: ArgumentParser):
+        parser.add_class_arguments(annotated[NestedAnnotatedDataClassWithDefaultFactory, 1], "n")
+        cfg = parser.parse_args(["--n", "{}"])
+        assert cfg.n == Namespace(a1=Namespace(a2=1))
+
 
-annotated = typing_extensions_import("Annotated")
 length = "length"
 if pydantic_support:
     import pydantic
@@ -480,6 +584,26 @@ if pydantic_support:
         p1: float = 0.1
         p2: str = "-"
 
+    @pydantic.dataclasses.dataclass
+    class PydanticDataNested:
+        p3: PydanticData
+
+    if pydantic_supports_field_init:
+        from pydantic.dataclasses import dataclass as pydantic_v2_dataclass
+        from pydantic.fields import Field as PydanticV2Field
+
+        @pydantic_v2_dataclass
+        class PydanticDataFieldInitFalse:
+            p1: str = PydanticV2Field("-", init=False)
+
+    @pydantic.dataclasses.dataclass
+    class PydanticDataStdlibField:
+        p1: str = dataclasses.field(default="-")
+
+    @pydantic.dataclasses.dataclass
+    class PydanticDataStdlibFieldWithFactory:
+        p1: str = dataclasses.field(default_factory=lambda: "-")
+
     class PydanticModel(pydantic.BaseModel):
         p1: str
         p2: int = 3
@@ -587,6 +711,43 @@ class TestPydantic:
             parser.parse_args([f"--model.param={invalid_value}"])
         ctx.match("model.param")
 
+    @pytest.mark.skipif(not pydantic_supports_field_init, reason="Field.init is required")
+    def test_dataclass_field_init_false(self, parser):
+        # Prior to PR #480, this test would produce the following error:
+        #
+        # TypeError: Parser key "data.p1":
+        # Expected a <class 'str'>. Got value: annotation=str
+        # required=False default='-' init=False
+        parser.add_argument("--data", type=PydanticDataFieldInitFalse)
+        help_str = get_parser_help(parser)
+        assert "--data.p1" not in help_str
+        cfg = parser.parse_args(["--data", "{}"])
+        assert cfg.data == Namespace()
+
+        cfg = parser.instantiate_classes(cfg)
+        assert cfg.data.p1 == "-"
+
+    def test_dataclass_stdlib_field(self, parser):
+        parser.add_argument("--data", type=PydanticDataStdlibField)
+        cfg = parser.parse_args(["--data", "{}"])
+        assert cfg.data == Namespace(p1="-")
+
+    def test_dataclass_stdlib_field_init_with_factory(self, parser):
+        parser.add_argument("--data", type=PydanticDataStdlibFieldWithFactory)
+        cfg = parser.parse_args(["--data", "{}"])
+        assert cfg.data == Namespace(p1="-")
+
+    def test_dataclass_nested(self, parser):
+        # Prior to PR #480, this test would produce the following error:
+        #
+        # ValueError: Expected "default" argument to be an instance of
+        # "PydanticData" or its kwargs dict, given
+        # <dataclasses._MISSING_TYPE object at 0x105624c50>
+
+        parser.add_argument("--data", type=PydanticDataNested)
+        cfg = parser.parse_args(["--data", '{"p3": {"p1": 1.0}}'])
+        assert cfg.data == Namespace(p3=Namespace(p1=1.0, p2="-"))
+
 
 # attrs tests
 
@@ -606,6 +767,13 @@ if attrs_support:
     class AttrsFieldFactory:
         p1: List[str] = attrs.field(factory=lambda: ["one", "two"])
 
+    @attrs.define
+    class AttrsFieldInitFalse:
+        p1: dict = attrs.field(init=False)
+
+        def __attrs_post_init__(self):
+            self.p1 = {}
+
 
 @pytest.mark.skipif(not attrs_support, reason="attrs package is required")
 class TestAttrs:
@@ -628,3 +796,17 @@ class TestAttrs:
         assert cfg1.data.p1 == ["one", "two"]
         assert cfg1.data.p1 == cfg2.data.p1
         assert cfg1.data.p1 is not cfg2.data.p1
+
+    def test_field_init_false(self, parser):
+        # Prior to PR #480, this test would produce the following error:
+        #
+        # TypeError('Validation failed: Key "data.p1" is required but
+        # not included in config object or its value is None.')
+
+        parser.add_argument("--data", type=AttrsFieldInitFalse)
+        cfg = parser.parse_args(["--data", "{}"])
+        help_str = get_parser_help(parser)
+        assert "--data.p1" not in help_str
+        assert cfg.data == Namespace()
+        cfg = parser.instantiate_classes(cfg)
+        assert cfg.data.p1 == {}
diff --git a/jsonargparse_tests/test_paths.py b/jsonargparse_tests/test_paths.py
index 7843e3d..5020669 100644
--- a/jsonargparse_tests/test_paths.py
+++ b/jsonargparse_tests/test_paths.py
@@ -3,10 +3,8 @@ from __future__ import annotations
 import json
 import os
 from calendar import Calendar
-from io import StringIO
 from pathlib import Path
 from typing import Any, Dict, List, Optional
-from unittest.mock import patch
 
 import pytest
 
@@ -145,7 +143,7 @@ def test_enable_path_subclass(parser, tmp_cwd):
     assert isinstance(init["cal"], Calendar)
 
 
-def test_enable_path_list_path_fr(parser, tmp_cwd, subtests):
+def test_enable_path_list_path_fr(parser, tmp_cwd, mock_stdin, subtests):
     tmpdir = tmp_cwd / "subdir"
     tmpdir.mkdir()
     (tmpdir / "file1").touch()
@@ -180,14 +178,14 @@ def test_enable_path_list_path_fr(parser, tmp_cwd, subtests):
         assert ["file1", "file2", "file3", "file4"] == [str(x) for x in cfg.list]
 
     with subtests.test("paths list from stdin"):
-        with patch("sys.stdin", StringIO("file1\nfile2\n")):
+        with mock_stdin("file1\nfile2\n"):
             with Path_drw("subdir").relative_path_context():
                 cfg = parser.parse_args(["--list", "-"])
         assert all(isinstance(x, Path_fr) for x in cfg.list)
         assert ["file1", "file2"] == [str(x) for x in cfg.list]
 
     with subtests.test("paths list from stdin path not exist"):
-        with patch("sys.stdin", StringIO("file1\nfile2\n")):
+        with mock_stdin("file1\nfile2\n"):
             with pytest.raises(ArgumentError) as ctx:
                 parser.parse_args(["--list", "-"])
             ctx.match("File does not exist")
@@ -215,6 +213,30 @@ def test_enable_path_list_path_fr(parser, tmp_cwd, subtests):
         pytest.raises(ArgumentError, lambda: parser.parse_args(["--lists", "no-such-file"]))
 
 
+def test_enable_path_list_path_fr_default_stdin(parser, tmp_cwd, mock_stdin, subtests):
+    (tmp_cwd / "file1").touch()
+    (tmp_cwd / "file2").touch()
+
+    parser.add_argument(
+        "--list",
+        type=List[Path_fr],
+        enable_path=True,
+        default="-",
+    )
+
+    with subtests.test("without args"):
+        with mock_stdin("file1\nfile2\n"):
+            cfg = parser.parse_args([])
+        assert all(isinstance(x, Path_fr) for x in cfg.list)
+        assert ["file1", "file2"] == [str(x) for x in cfg.list]
+
+    with subtests.test("stdin arg"):
+        with mock_stdin("file1\nfile2\n"):
+            cfg = parser.parse_args(["--list=-"])
+        assert all(isinstance(x, Path_fr) for x in cfg.list)
+        assert ["file1", "file2"] == [str(x) for x in cfg.list]
+
+
 class Data:
     def __init__(self, path: Optional[os.PathLike] = None):
         pass