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
39 changes: 39 additions & 0 deletions CHANGELOG.rst
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,45 @@ The semantic versioning only considers the public API as described in
paths are considered internals and can change in minor and patch releases.


v5.0.0 (unreleased)
-------------------

Changed
^^^^^^^
- The print config argument now defaults to ``--print_<config_arg_name>``,
instead of always being ``--print_config`` (`#969
<https://github.com/mauvilsa/jsonargparse/pull/969>`__).
- ``instantiate_subclass_spec_in_any`` now defaults to ``False``, so a subclass
spec given for a type that accepts any value is kept as is, instead of being
imported and instantiated (`#969
<https://github.com/mauvilsa/jsonargparse/pull/969>`__).
- Import paths denied by ``import_path_denylist`` now always fail, instead of
only failing when ``import_path_denylist`` or ``import_path_allowlist`` is
given a value (`#969 <https://github.com/mauvilsa/jsonargparse/pull/969>`__).
- In the add signature methods, a parameter with an ``Optional`` type and no
default is now required, and with ``fail_untyped=False`` a required parameter
without a type annotation now gets type ``Untyped`` and stays required,
instead of both becoming optional with default ``None`` (`#969
<https://github.com/mauvilsa/jsonargparse/pull/969>`__).
- A config that has multiple subcommand settings now requires the subcommand to
be given explicitly, instead of taking the first one (`#969
<https://github.com/mauvilsa/jsonargparse/pull/969>`__).
- Config objects always include metadata, i.e. ``clone(with_meta=False)`` is now
the only way to strip it (`#969
<https://github.com/mauvilsa/jsonargparse/pull/969>`__).

Removed
^^^^^^^
- All features deprecated in v4 are now removed, see :ref:`migrate-v5` for the
complete list and how to update code (`#969
<https://github.com/mauvilsa/jsonargparse/pull/969>`__).
- ``ruyaml`` extras require, superseded by ``ruamel`` (`#969
<https://github.com/mauvilsa/jsonargparse/pull/969>`__).
- The ``yaml.SafeDumper`` representer for ``Namespace``, which was only added
for backward compatibility in pytorch-lightning (`#969
<https://github.com/mauvilsa/jsonargparse/pull/969>`__).


v4.52.0 (2026-09-01)
--------------------

Expand Down
31 changes: 10 additions & 21 deletions DOCUMENTATION.rst
Original file line number Diff line number Diff line change
Expand Up @@ -2172,14 +2172,11 @@ would also accept subclasses of ``MyClass``, and the config would be:
.. note::

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
``instantiate_subclass_spec_in_any=False`` in :func:`.set_parsing_settings`
to get this behavior now and silence the deprecation warning. Setting it to
``True`` keeps the instantiation, but is discouraged, since it means that a
config can instantiate any class, which is a security risk.
``class_path`` and ``init_args``, and the spec is kept as is, so that the
code receiving it decides whether to instantiate it. Set
``instantiate_subclass_spec_in_any=True`` in :func:`.set_parsing_settings`
to have ``instantiate`` build the class, though this is discouraged, since
it means that a config can instantiate any class, which is a security risk.

A value that looks like a subclass spec, i.e. has a ``class_path``, but
can't be parsed as one, e.g. because the class fails to import, is by
Expand Down Expand Up @@ -2276,11 +2273,11 @@ The denylist is not the only thing that limits what a config can reach. Type
hints do as well, since a ``class_path`` is only accepted where the annotation
allows one, and must name a subclass of the annotated type. The exceptions are
``Any`` and ``object``, which accept a subclass spec of any class, see
:ref:`sub-classes`. Setting ``instantiate_subclass_spec_in_any=False``, which is
the default from v5.0.0, keeps these values as plain dicts, so nothing is
imported or instantiated and the code that receives the dict decides what to do
with it. The denylist still applies when ``validate_subclass_spec_in_any=True``,
since validating a spec requires importing the class it names.
:ref:`sub-classes`. By default ``instantiate_subclass_spec_in_any`` is
``False``, so these values are kept as plain dicts, nothing is imported or
instantiated and the code that receives the dict decides what to do with it.
The denylist still applies when ``validate_subclass_spec_in_any=True``, since
validating a spec requires importing the class it names.

.. note::

Expand All @@ -2299,14 +2296,6 @@ since validating a spec requires importing the class it names.
into the config, and the resolvers that the application registers are
equally reachable. Avoid these parser modes for untrusted configs.

.. note::

Until v5.0.0 a denied import path only gives a deprecation warning and the
import proceeds, so that existing configs don't break. Giving a value to
``import_path_denylist`` or ``import_path_allowlist``, an empty list
included, makes denied import paths fail instead. From v5.0.0 they always
fail.


.. _sub-config-files:

Expand Down
3 changes: 0 additions & 3 deletions jsonargparse/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@
from ._cli import CLI # noqa: F401
from ._common import * # noqa: F403
from ._core import * # noqa: F403
from ._deprecated import * # noqa: F403
from ._formatters import * # noqa: F403
from ._from_config import * # noqa: F403
from ._instantiation import * # noqa: F403
Expand Down Expand Up @@ -43,7 +42,6 @@
_cli,
_common,
_core,
_deprecated,
_formatters,
_from_config,
_instantiation,
Expand All @@ -68,7 +66,6 @@
__all__ += _instantiation.__all__
__all__ += _loaders_dumpers.__all__
__all__ += _util.__all__
__all__ += _deprecated.__all__


__version__ = "4.52.0"
26 changes: 2 additions & 24 deletions jsonargparse/_actions.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
"""Collection of useful actions to define arguments."""

import inspect
import os
import re
import sys
from argparse import SUPPRESS, _HelpAction, _VersionAction
Expand Down Expand Up @@ -118,21 +117,6 @@ def _add_print_config_argument(container, action):
if isinstance(action, ActionConfigFile) and getattr(container, "_print_config", None) is not None:
if "%s" in container._print_config:
container._print_config = container._print_config % action.dest
elif (
container._print_config == "--print_config"
and action.dest != "config"
and os.getenv("JSONARGPARSE_DEPRECATION_WARNINGS", "").lower() == "all"
):
from ._deprecated import deprecation_warning

deprecation_warning(
"print_config_default_name",
"From v5.0.0 the print config argument will by default reuse the name of the config "
'argument as "--print_%s". The current default is always "--print_config", but in v5.0.0 '
f'with a config argument named "{action.dest}" it will become "--print_{action.dest}". '
'To keep the current name set print_config="--print_config" explicitly.',
stacklevel=2,
)
assert container._print_config.startswith("--")
container.add_argument(container._print_config, action=_ActionPrintConfig)

Expand Down Expand Up @@ -204,22 +188,16 @@ def __init__(
)

def __call__(self, parser, namespace, value, option_string=None):
from ._deprecated import deprecated_skip_null, deprecated_valid_flags

kwargs = {"subparser": parser, "key": None, "skip_unset": False, "skip_validation": False}
valid_flags = {"": None, "skip_default": "skip_default", "skip_unset": "skip_unset"} | deprecated_valid_flags
valid_flags = {"": None, "skip_default": "skip_default", "skip_unset": "skip_unset"}
if ruamel_support:
valid_flags["comments"] = "with_comments"
flags = value[0].split(",")
invalid_flags = [f for f in flags if f not in valid_flags]
if len(invalid_flags) > 0:
raise argument_error(f'Invalid option "{invalid_flags[0]}" for {option_string}')
for flag in [f for f in flags if f != ""]:
mapped = valid_flags[flag]
if deprecated_skip_null(flag):
kwargs["skip_unset"] = True
else:
kwargs[mapped] = True
kwargs[valid_flags[flag]] = True
while hasattr(parser, "parent_parser"):
kwargs["key"] = parser.subcommand if kwargs["key"] is None else parser.subcommand + "." + kwargs["key"]
parser = parser.parent_parser
Expand Down
17 changes: 2 additions & 15 deletions jsonargparse/_cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@

from ._actions import ActionConfigFile, _ActionPrintConfig, remove_actions
from ._core import ArgumentParser
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
Expand All @@ -22,11 +21,11 @@

def CLI(*args, **kwargs):
"""Alias of :func:`auto_cli`."""
return auto_cli(*args, _stacklevel=3, **kwargs)
return auto_cli(*args, **kwargs)


def auto_cli(
components: ComponentsType = None,
components: ComponentsType,
args: list[str] | None = None,
config_help: str = default_config_option_help,
set_defaults: dict[str, Any] | None = None,
Expand Down Expand Up @@ -65,12 +64,6 @@ def auto_cli(
Returns:
The value returned by the executed function or class method.
"""
return_parser = kwargs.pop("return_parser", False)
stacklevel = kwargs.pop("_stacklevel", 2)

if components is None:
components = get_implicit_auto_cli_components(stacklevel)

if isinstance(components, list) and len(components) == 1:
components = components[0]

Expand All @@ -95,9 +88,6 @@ def auto_cli(
_add_component_to_parser(components, parser, as_positional, return_instance, fail_untyped, config_help)
if set_defaults is not None:
parser.set_defaults(set_defaults)
if return_parser:
deprecation_warning_cli_return_parser(stacklevel)
return parser
cfg = parser.parse_args(args)
init = parser.instantiate(cfg)
return _run_component(components, init)
Expand All @@ -109,9 +99,6 @@ def auto_cli(

if set_defaults is not None:
parser.set_defaults(set_defaults)
if return_parser:
deprecation_warning_cli_return_parser(stacklevel)
return parser
cfg = parser.parse_args(args)
init = parser.instantiate(cfg)
components_ns = dict_to_namespace(components)
Expand Down
36 changes: 6 additions & 30 deletions jsonargparse/_common.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@
from contextvars import ContextVar
from typing import ( # type: ignore[attr-defined]
Generic,
Protocol,
TypeVar,
_GenericAlias,
)
Expand Down Expand Up @@ -68,14 +67,6 @@ def __bool__(self):
Unset = _UnsetType()


class InstantiatorCallable(Protocol):
def __call__(self, class_type: type[ClassType], *args, **kwargs) -> ClassType:
pass # pragma: no cover


InstantiatorsDictType = dict[tuple[type, bool], InstantiatorCallable]


parent_parser: ContextVar[ArgumentParser | None] = ContextVar("parent_parser", default=None)
parser_capture: ContextVar[bool] = ContextVar("parser_capture", default=False)
defaults_cache: ContextVar[Namespace | None] = ContextVar("defaults_cache", default=None)
Expand All @@ -84,7 +75,6 @@ def __call__(self, class_type: type[ClassType], *args, **kwargs) -> ClassType:
single_subcommand: ContextVar[bool] = ContextVar("single_subcommand", default=True)
validating_defaults: ContextVar[bool] = ContextVar("validating_defaults", default=False)
load_value_mode: ContextVar[str | None] = ContextVar("load_value_mode", default=None)
class_instantiators: ContextVar[InstantiatorsDictType | None] = ContextVar("class_instantiators", default=None)
nested_links: ContextVar[list[dict]] = ContextVar("nested_links", default=[])
applied_instantiation_links: ContextVar[set | None] = ContextVar("applied_instantiation_links", default=None)
path_dump_preserve_relative: ContextVar[bool] = ContextVar("path_dump_preserve_relative", default=False)
Expand All @@ -99,7 +89,6 @@ def __call__(self, class_type: type[ClassType], *args, **kwargs) -> ClassType:
"single_subcommand": single_subcommand,
"validating_defaults": validating_defaults,
"load_value_mode": load_value_mode,
"class_instantiators": class_instantiators,
"nested_links": nested_links,
"applied_instantiation_links": applied_instantiation_links,
"path_dump_preserve_relative": path_dump_preserve_relative,
Expand Down Expand Up @@ -280,14 +269,13 @@ class ImportDenied(ImportError, ValueError):
parsing_settings: dict = {
"validate_defaults": False,
"validate_subclass_spec_in_any": False,
"instantiate_subclass_spec_in_any": None, # v5.0.0: change default to False
"instantiate_subclass_spec_in_any": False,
"parse_optionals_as_positionals": False,
"add_print_completion_argument": False,
"stubs_resolver_allow_py_files": False,
"omegaconf_absolute_to_relative_paths": False,
"unset_sentinel": None,
"import_path_verdicts": {entry: False for entry in default_import_path_denylist},
"import_paths_enforced": False, # v5.0.0: change default to True
}


Expand Down Expand Up @@ -321,7 +309,6 @@ def set_import_path_verdicts(denylist: list[str] | None, allowlist: list[str] |
else:
logger.debug(f"Import path {entry!r} changed to {state}")
parsing_settings["import_path_verdicts"] = verdicts
parsing_settings["import_paths_enforced"] = True


def denying_import_path_entry(path: str) -> str | None:
Expand All @@ -344,11 +331,7 @@ def check_import_path(path: str) -> None:
f"Importing '{path}' is not allowed, denied by the {entry!r} entry of the import path denylist. "
"Add the import path to import_path_allowlist in set_parsing_settings to allow it."
)
if get_parsing_setting("import_paths_enforced"):
raise ImportDenied(message)
from ._deprecated import import_paths_not_enforced

import_paths_not_enforced(path, message)
raise ImportDenied(message)


def get_env_var_bool(name: str) -> bool:
Expand Down Expand Up @@ -396,12 +379,10 @@ def set_parsing_settings(
instantiate_subclass_spec_in_any: Whether ``instantiate`` builds the
class when a value for a type that accepts any value, i.e. ``Any``,
``object`` or ``Unvalidated<...>``, is a valid subclass spec. If
``False``, the value is kept as a subclass spec, which the code that
receives it can instantiate itself if desired. Currently the default
is ``True`` and a deprecation warning is emitted, since from v5.0.0
the default will be ``False``. Enabling it is discouraged because it
means that any class can be instantiated, so only do it for trusted
configs.
``False``, the default, the value is kept as a subclass spec, which
the code that receives it can instantiate itself if desired.
Enabling it is discouraged because it means that any class can be
instantiated, so only do it for trusted configs.
config_read_mode_urls_enabled: Whether to read config files from URLs
using requests package. Default is ``False``.
config_read_mode_fsspec_enabled: Whether to read config files from
Expand Down Expand Up @@ -779,11 +760,6 @@ def logger(self) -> logging.Logger:

@logger.setter
def logger(self, logger: bool | str | dict | logging.Logger):
if logger is None:
from ._deprecated import deprecation_warning, logger_property_none_message

deprecation_warning((LoggerProperty.logger, None), logger_property_none_message, stacklevel=6)
logger = False
if not logger and debug_mode_active():
logger = {"level": "DEBUG"}
self._logger = parse_logger(logger, type(self).__name__)
Expand Down
17 changes: 2 additions & 15 deletions jsonargparse/_completions.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
from subprocess import PIPE, Popen
from typing import Literal, Union

from ._actions import ActionConfigFile, ActionFail, _ActionConfigLoad, _ActionHelpClassPath, remove_actions
from ._actions import ActionConfigFile, _ActionConfigLoad, _ActionHelpClassPath, remove_actions
from ._common import (
NonParsingAction,
get_optionals_as_positionals_actions,
Expand Down Expand Up @@ -47,17 +47,7 @@ def add_print_completion_argument(parser):
if getattr(parser, "parent_parser", None):
return
print_completion_argument = get_parsing_setting("add_print_completion_argument")
if not print_completion_argument and "--print_shtab" not in parser._option_string_actions:
parser.add_argument(
"--print_shtab",
action=ActionFail(
message="%(option)s is no longer supported. Use set_parsing_settings("
"add_print_completion_argument=True) or "
"JSONARGPARSE_ADD_PRINT_COMPLETION_ARGUMENT=true to add --print_completion."
),
help=argparse.SUPPRESS,
)
elif print_completion_argument and "--print_completion" not in parser._option_string_actions:
if print_completion_argument and "--print_completion" not in parser._option_string_actions:
parser.add_argument("--print_completion", action=PrintCompletionAction)


Expand Down Expand Up @@ -197,9 +187,6 @@ def norm_name(name: str) -> str:

def shtab_prepare_actions(parser) -> None:
remove_actions(parser, (PrintCompletionAction,))
legacy_action = parser._option_string_actions.get("--print_shtab")
if legacy_action and legacy_action in parser._actions:
parser._actions.remove(legacy_action)
if parser._subcommands_action:
for subparser in parser._subcommands_action._name_parser_map.values():
shtab_prepare_actions(subparser)
Expand Down
Loading