Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions CHANGELOG.rst
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,10 @@ Added
function with a compatible signature, so the import path of a function is
accepted as value, see :ref:`type-hints` (`#963
<https://github.com/mauvilsa/jsonargparse/pull/963>`__).
- New ``fail_untyped="all"`` for the add signature methods and ``auto_cli``,
which raises an exception for all parameters that don't have a type
annotation, not only the required ones (`#965
<https://github.com/mauvilsa/jsonargparse/pull/965>`__).

Fixed
^^^^^
Expand Down Expand Up @@ -86,6 +90,17 @@ Changed
(`#964 <https://github.com/mauvilsa/jsonargparse/pull/964>`__).
- New :ref:`migrate-v5` guide that describes what needs to be changed to migrate
from v4 to v5 (`#964 <https://github.com/mauvilsa/jsonargparse/pull/964>`__).
- Signature parameters without a type annotation now get type ``Untyped``
instead of ``Any``, so that the help makes evident that the value is not
validated. A parameter that has a default gets ``Union[<type of the default>,
Untyped]`` as before, now also with ``fail_untyped=False``, which previously
gave ``Any`` (`#965 <https://github.com/mauvilsa/jsonargparse/pull/965>`__).
- New debug logs for the cases in which the type or the requiredness of a
signature parameter is not what the signature says: no type annotation, a
parameter skipped because its name starts with ``_``, a ``None`` default that
makes the type optional, a ``NotRequired`` parameter without a default and a
parameter that is the target of a link (`#965
<https://github.com/mauvilsa/jsonargparse/pull/965>`__).

Deprecated
^^^^^^^^^^
Expand Down
81 changes: 35 additions & 46 deletions DOCUMENTATION.rst
Original file line number Diff line number Diff line change
Expand Up @@ -635,53 +635,43 @@ an argument of type ``Union[int, list[int]]``, ``--val=1`` gives ``1``, while
Unvalidated types
-----------------

When arguments are added from a signature, i.e. :meth:`add_function_arguments
<.ArgumentParser.add_function_arguments>`, :meth:`add_method_arguments
<.ArgumentParser.add_method_arguments>`, :meth:`add_class_arguments
<.ArgumentParser.add_class_arguments>` or a parameter of a :ref:`subclass type
<sub-classes>`, some parameters can have a type that jsonargparse can't
validate. The same holds for the keys of a ``TypedDict``, however the argument
is added. Skipping these parameters would make it impossible to give them at
all, so instead only the parts of the type that can't be validated are replaced
by a type that accepts any value. The help shows these parts as
``Unvalidated<...>``, keeping the name used in the source code. For example, a
class with an ``items: list[SomeType] = []`` parameter for which ``SomeType``
can't be validated is shown in the help as:
A :ref:`signature parameter <classes-methods-functions>` or a ``TypedDict`` key
can have a type that jsonargparse can't validate. The argument is still added,
with only the parts of the type that can't be validated replaced by a type that
accepts any value. The help shows these parts as ``Unvalidated<...>``, keeping
the name used in the source code. For example, a class with a parameter
``items: list[SomeType] = []`` for which ``SomeType`` can't be validated is
shown in the help as:

.. code-block:: text

--myclass.items ITEMS (type: list[Unvalidated<SomeType>], default: [])

A type or a part of it can't be validated when:
Only these parts accept any value: in the example the value must still be a
list, and in a ``Union`` the other subtypes are still validated. A type or a
part of it can't be validated when:

- It failed to resolve, e.g. a missing import or a typo in a postponed
annotation.
- It is not a type that jsonargparse supports, e.g. a ``TypeVar`` that stands
for nothing, see :ref:`generic-types`.

To know which of the two it is for a given parameter, enable debug level
logging, see :ref:`logging`. The debug log gives the reason for each part of the
type that can't be validated.

Only these parts accept any value. In the example above the value must still be
a list, only its items are not validated. Likewise, in a ``Union`` only the
subtypes that can't be validated accept any value, the others are validated as
usual.
The debug log gives the reason for each part, see :ref:`logging`. A parameter
without a type annotation is shown as ``Untyped`` and behaves the same, see
:ref:`classes-methods-functions`.

Since there is no type to serialize with, :meth:`dump <.ArgumentParser.dump>`
and ``--print_config`` derive a type from the value itself, so that it is
serialized as it would be for an argument of that type. A value of a type that
jsonargparse doesn't support, e.g. a default that is an arbitrary object, is
serialized like the instances given for a :ref:`subclass type <sub-classes>`: as
an import path when the value can be imported back, and otherwise as a message
saying that it was not serializable, together with a warning.
and ``--print_config`` derive a type from the value itself. A value of a type
that jsonargparse doesn't support, e.g. an arbitrary object, is serialized like
the instances given for a :ref:`subclass type <sub-classes>`: as an import path
when it can be imported back, and otherwise as a message saying that it was not
serializable, together with a warning.

Parsing a dump back has no type to validate with either, so only the values that
the config formats represent round-trip. For instance, a ``set`` is dumped as a
list and parses back as a list, and an ``Enum`` member is dumped as its name and
parses back as a string. A warning is raised for each dumped value that loses
its type this way. All of the above applies equally to arguments typed as
``Any``/``object``.
the config formats represent round-trip, e.g. a ``set`` is dumped and parsed
back as a list, and an ``Enum`` member as its name. A warning is raised for each
dumped value that loses its type this way. All of the above applies equally to
``Any`` and ``object``.


.. _restricted-numbers:
Expand Down Expand Up @@ -1556,17 +1546,17 @@ instantiation and for the method call.
A wide range of type hints is supported for signature parameters, see
:ref:`type-hints`. Notes about the add signature methods:

- With the default ``fail_untyped=True``, all required parameters must have a
type, otherwise an exception is raised. Positional-only parameters are always
required.
- A parameter without a type annotation, or with a type that can only be
validated in part, is added with a type that accepts any value, see
:ref:`unvalidated-types`. Without an annotation but with a default, the type
is ``Union[<type of the default>, Untyped]``, i.e. a value is converted to the
default's type when it accepts it.

- A parameter that has a default but no type annotation is added with type
``Union[<type of the default>, Any]``, so any value is accepted. With
``fail_untyped=False``, a required parameter without a type gets type ``Any``.

- A parameter whose type can only be validated in part is added with the
remaining parts replaced by a type that accepts any value, see
:ref:`unvalidated-types`.
- ``fail_untyped`` decides which parameters without a type annotation raise an
exception instead: the required ones with the default ``True``, all of them
with ``"all"``, and none with ``False``. Positional-only parameters are always
required. Use ``"all"`` only for code you own, since one untyped parameter of
a dependency would make its signature impossible to add.

- Parameters whose name starts with ``_`` are considered internal and skipped,
unless they are required.
Expand Down Expand Up @@ -1992,7 +1982,7 @@ Most of the Python standard library has its types in stubs, for example:

Without the stubs resolver, that :meth:`add_function_arguments
<.ArgumentParser.add_function_arguments>` call needs ``fail_untyped=False``, and
then ``a`` and ``b`` get type ``Any`` instead of ``float``, so an invalid value
then ``a`` and ``b`` get ``Untyped`` instead of ``float``, so an invalid value
such as a string would not fail.

The defaults of parameters found only through stubs are not known. The help then
Expand Down Expand Up @@ -2154,9 +2144,8 @@ would also accept subclasses of ``MyClass``, and the config would be:

.. note::

A parameter of type ``Any`` or ``object``, which is also what
``fail_untyped=False`` gives, accepts a dict with ``class_path`` and
``init_args``, and the class is parsed and instantiated.
A parameter of type ``Any``, ``object``, or ``Untyped``, accepts a dict with
``class_path`` and ``init_args``, and the class is parsed and instantiated.

This instantiation is deprecated. From v5.0.0 the subclass spec is kept as
is, so that the code receiving it decides whether to instantiate it. Set
Expand Down
10 changes: 6 additions & 4 deletions jsonargparse/_cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
from ._deprecated import deprecation_warning_cli_return_parser, get_implicit_auto_cli_components
from ._namespace import Namespace, dict_to_namespace
from ._optionals import get_doc_short_description
from ._signatures import FailUntyped
from ._util import capture_parser, default_config_option_help

__all__ = ["auto_cli", "auto_parser"]
Expand All @@ -31,7 +32,7 @@ def auto_cli(
set_defaults: dict[str, Any] | None = None,
as_positional: bool = True,
return_instance: bool = False,
fail_untyped: bool = True,
fail_untyped: FailUntyped = True,
parser_class: type[ArgumentParser] = ArgumentParser,
**kwargs,
):
Expand All @@ -56,7 +57,8 @@ def auto_cli(
as_positional: Whether to add required parameters as positional arguments.
return_instance: Whether class components should be instantiated directly and returned,
i.e. without exposing class methods as subcommands.
fail_untyped: Whether to raise exception if a required parameter does not have a type.
fail_untyped: Whether to raise an exception for parameters that don't have a type:
True for the required ones, "all" for all of them, False for none.
parser_class: The :class:`ArgumentParser` subclass to use.
**kwargs: Used to instantiate :class:`.ArgumentParser`.

Expand Down Expand Up @@ -147,7 +149,7 @@ def _add_subcommands(
config_help: str,
as_positional: bool,
return_instance: bool,
fail_untyped: bool,
fail_untyped: FailUntyped,
) -> None:
subcommands = parser.add_subcommands(required=True)
for name, component in components.items():
Expand Down Expand Up @@ -177,7 +179,7 @@ def _add_component_to_parser(
parser: ArgumentParser,
as_positional: bool,
return_instance: bool,
fail_untyped: bool,
fail_untyped: FailUntyped,
config_help: str,
):
kwargs: dict = {"as_positional": as_positional, "fail_untyped": fail_untyped, "sub_configs": True}
Expand Down
Loading