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
4 changes: 4 additions & 0 deletions CHANGELOG.rst
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,10 @@ Deprecated
- ``compose_dataclasses`` is deprecated and will be removed in v5.0.0. There is
no direct replacement, whoever is interested can copy the code (`#796
<https://github.com/omni-us/jsonargparse/pull/796>`__).
- ``dict_to_namespace`` is deprecated and will be removed in v5.0.0. No
replacement is provided because blindly converting a dictionary to a namespace
may not yield the same results as using a parser, which could lead to
confusion. (`#797 <https://github.com/omni-us/jsonargparse/pull/797>`__).


v4.42.0 (2025-10-14)
Expand Down
20 changes: 17 additions & 3 deletions jsonargparse/_deprecated.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
from importlib import import_module
from pathlib import Path
from types import ModuleType
from typing import Any, Callable, Dict, Optional, Set, overload
from typing import Any, Callable, Dict, Optional, Set, Union, overload

from ._common import Action, null_logger
from ._common import LoggerProperty as InternalLoggerProperty
Expand All @@ -28,6 +28,7 @@
"ParserError",
"compose_dataclasses",
"get_config_read_mode",
"dict_to_namespace",
"namespace_to_dict",
"null_logger",
"set_docstring_parse_options",
Expand Down Expand Up @@ -123,8 +124,6 @@ def patched_init(self, *args, parse_as_dict: bool = False, **kwargs):
ArgumentParser._unpatched_init = ArgumentParser.__init__
ArgumentParser.__init__ = patched_init

from typing import Union

# Patch parse methods
def patch_parse_method(method_name):
unpatched_method_name = "_unpatched_" + method_name
Expand Down Expand Up @@ -707,6 +706,21 @@ def namespace_to_dict(namespace: Namespace) -> Dict[str, Any]:
return namespace.clone().as_dict()


@deprecated(
"""
dict_to_namespace was deprecated in v4.43.0 and will be removed in v5.0.0.
No replacement is provided because blindly converting a dictionary to a
namespace may not yield the same results as using a parser, which could lead
to confusion.
"""
)
def dict_to_namespace(cfg_dict: dict[str, Any]) -> Namespace:
"""Converts a nested dictionary into a nested namespace."""
from ._namespace import dict_to_namespace as _dict_to_namespace

return _dict_to_namespace(cfg_dict)


@overload
def strip_meta(cfg: "Namespace") -> "Namespace": ... # pragma: no cover

Expand Down
28 changes: 9 additions & 19 deletions jsonargparse/_namespace.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,7 @@
Union,
)

__all__ = [
"Namespace",
"dict_to_namespace",
]
__all__ = ["Namespace"]


meta_keys = {"__default_config__", "__path__", "__orig__"}
Expand Down Expand Up @@ -322,27 +319,20 @@ def del_clash_mark(key: str) -> str:
return key


def expand_dict(cfg):
for k, v in cfg.items():
def expand_dict(data: dict) -> Namespace:
for k, v in data.items():
if isinstance(v, dict) and all(isinstance(k, str) for k in v):
cfg[k] = expand_dict(v)
data[k] = expand_dict(v)
elif isinstance(v, list):
for nn, vv in enumerate(v):
if isinstance(vv, dict) and all(isinstance(k, str) for k in vv):
cfg[k][nn] = expand_dict(vv)
return Namespace(**cfg)
data[k][nn] = expand_dict(vv)
return Namespace(**data)


def dict_to_namespace(cfg_dict: Union[Dict[str, Any], Namespace]) -> Namespace:
"""Converts a nested dictionary into a nested namespace.

Note: Using this function is generally discouraged because it may not
produce the same results as a parser would. However, it remains part
of the public API to support valid use cases and ensure backward
compatibility.
"""
cfg_dict = recreate_branches(cfg_dict)
return expand_dict(cfg_dict)
def dict_to_namespace(data: dict[str, Any]) -> Namespace:
data = recreate_branches(data)
return expand_dict(data)


# Temporal to provide backward compatibility in pytorch-lightning
Expand Down
14 changes: 14 additions & 0 deletions jsonargparse_tests/test_deprecated.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
LoggerProperty,
ParserError,
deprecation_warning,
dict_to_namespace,
namespace_to_dict,
shown_deprecation_warnings,
strip_meta,
Expand Down Expand Up @@ -741,6 +742,19 @@ def test_add_dataclass_arguments(parser, subtests):
assert "CustomA title:" in help_str


def test_dict_to_namespace():
ns1 = Namespace(a=1, b=Namespace(c=2), d=[Namespace(e=3)])
dic = {"a": 1, "b": {"c": 2}, "d": [{"e": 3}]}
with catch_warnings(record=True) as w:
ns2 = dict_to_namespace(dic)
assert ns1 == ns2
assert_deprecation_warn(
w,
message="dict_to_namespace was deprecated",
code="ns2 = dict_to_namespace(dic)",
)


def test_namespace_to_dict():
ns = Namespace()
ns["w"] = 1
Expand Down
9 changes: 1 addition & 8 deletions jsonargparse_tests/test_namespace.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

import pytest

from jsonargparse import Namespace, dict_to_namespace
from jsonargparse import Namespace
from jsonargparse._namespace import NSKeyError, meta_keys

skip_if_no_setattr_insertion_order = pytest.mark.skipif(
Expand Down Expand Up @@ -223,13 +223,6 @@ def test_init_invalid():
Namespace(argparse.Namespace(), x=1)


def test_dict_to_namespace():
ns1 = Namespace(a=1, b=Namespace(c=2), d=[Namespace(e=3)])
dic = {"a": 1, "b": {"c": 2}, "d": [{"e": 3}]}
ns2 = dict_to_namespace(dic)
assert ns1 == ns2


def test_use_for_kwargs():
def func(a=1, b=2, c=3):
return a, b, c
Expand Down