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


v4.44.1 (unreleased)
--------------------

Fixed
^^^^^
- Evaluation of postponed annotations for dataclass inheritance across modules
not working correctly (`#814
<https://github.com/omni-us/jsonargparse/pull/814>`__).


v4.44.0 (2025-11-25)
--------------------

Expand Down
12 changes: 6 additions & 6 deletions jsonargparse/_postponed_annotations.py
Original file line number Diff line number Diff line change
Expand Up @@ -221,7 +221,11 @@ def type_requires_eval(typehint):


def get_global_vars(obj: Any, logger: Optional[logging.Logger]) -> dict:
global_vars = obj.__globals__.copy() if hasattr(obj, "__globals__") else {}
global_vars = getattr(obj, "__globals__", {}).copy()
if is_dataclass(obj):
next_mro = inspect.getmro(obj)[1] # type: ignore[arg-type]
if is_dataclass(next_mro):
global_vars.update(get_global_vars(next_mro, logger))
for key, value in vars(import_module(obj.__module__)).items(): # needed for pydantic-v1
if key not in global_vars:
global_vars[key] = value
Expand Down Expand Up @@ -284,11 +288,7 @@ def evaluate_postponed_annotations(params, component, parent, logger):
if not (params and any(type_requires_eval(p.annotation) for p in params)):
return
try:
if (
is_dataclass(parent)
and component.__name__ == "__init__"
and not component.__qualname__.startswith(parent.__name__ + ".")
):
if is_dataclass(parent) and component.__name__ == "__init__":
types = get_types(parent, logger)
else:
types = get_types(component, logger)
Expand Down
11 changes: 10 additions & 1 deletion jsonargparse_tests/test_dataclasses.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
typing_extensions_import,
)
from jsonargparse._signatures import convert_to_dict
from jsonargparse.typing import PositiveFloat, PositiveInt
from jsonargparse.typing import PositiveFloat, PositiveInt, restricted_number_type
from jsonargparse_tests.conftest import (
get_parse_args_stdout,
get_parser_help,
Expand All @@ -31,6 +31,15 @@

annotated = typing_extensions_import("Annotated")

BetweenThreeAndNine = restricted_number_type("BetweenThreeAndNine", float, [(">=", 3), ("<=", 9)])
ListPositiveInt = List[PositiveInt]


@dataclasses.dataclass
class DifferentModuleBaseData:
count: Optional[BetweenThreeAndNine] = None # type: ignore[valid-type]
numbers: ListPositiveInt = dataclasses.field(default_factory=list)


@dataclasses.dataclass(frozen=True)
class DataClassA:
Expand Down
20 changes: 20 additions & 0 deletions jsonargparse_tests/test_postponed_annotations.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
)
from jsonargparse.typing import Path_drw
from jsonargparse_tests.conftest import capture_logs, source_unavailable
from jsonargparse_tests.test_dataclasses import DifferentModuleBaseData


def function_pep604(p1: str | None, p2: int | float | bool = 1):
Expand Down Expand Up @@ -324,3 +325,22 @@ def test_add_dataclass_with_init_pep585(parser, tmp_cwd):
parser.add_class_arguments(DataWithInit585, "data")
cfg = parser.parse_args(["--data.a=[1, 2]", "--data.b=."])
assert cfg.data == Namespace(a=[1, 2], b=Path_drw("."))


@dataclasses.dataclass
class InheritDifferentModule(DifferentModuleBaseData):
extra: str = "default"


def test_get_params_dataclass_inherit_different_module():
assert "BetweenThreeAndNine" not in globals()
assert "PositiveInt" not in globals()

params = get_params(InheritDifferentModule)

assert [p.name for p in params] == ["count", "numbers", "extra"]
assert all(not isinstance(p.annotation, str) for p in params)
assert not isinstance(params[0].annotation.__args__[0], str)
assert "BetweenThreeAndNine" in str(params[0].annotation)
assert not isinstance(params[1].annotation.__args__[0], str)
assert "PositiveInt" in str(params[1].annotation)
Loading