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
9 changes: 6 additions & 3 deletions jsonargparse_tests/test_core.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
import os
import pickle
import sys
from calendar import Calendar
from contextlib import redirect_stderr
from io import StringIO
from pathlib import Path
Expand Down Expand Up @@ -154,7 +153,7 @@
def test_parse_args_missing_required_lists_all(parser):
parser.add_argument("req_pos")
parser.add_argument("--req_opt", required=True)
with pytest.raises(TypeError, match="the following arguments are required: req_pos, req_opt"):

Check warning on line 156 in jsonargparse_tests/test_core.py

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Refactor this exception test to have only one invocation possibly throwing an exception.

See more on https://sonarcloud.io/project/issues?id=omni-us_jsonargparse&issues=AZ9kdIKdOrCLrsKFihzh&open=AZ9kdIKdOrCLrsKFihzh&pullRequest=928
parser.validate(Namespace())


Expand Down Expand Up @@ -210,7 +209,7 @@

def test_parse_args_choices_type_int(parser):
parser.add_argument("--ch", type=int, choices=[0, 1])
assert 0 == parser.parse_args(["--ch=0"]).ch

Check warning on line 212 in jsonargparse_tests/test_core.py

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Swap these 2 sides so they are in the correct order: actual value, expected value.

See more on https://sonarcloud.io/project/issues?id=omni-us_jsonargparse&issues=AZ9kdIKdOrCLrsKFihzj&open=AZ9kdIKdOrCLrsKFihzj&pullRequest=928


def test_parse_args_choices_type_union(parser):
Expand Down Expand Up @@ -485,10 +484,10 @@

# parse_args precedence
with subtests.test("parse_args parser default"):
assert "from parser default" == parser.parse_args([]).op1

Check warning on line 487 in jsonargparse_tests/test_core.py

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Swap these 2 sides so they are in the correct order: actual value, expected value.

See more on https://sonarcloud.io/project/issues?id=omni-us_jsonargparse&issues=AZ9kdIKdOrCLrsKFihzx&open=AZ9kdIKdOrCLrsKFihzx&pullRequest=928
with subtests.test("parse_args default config file"):
default_config_file.write_text(json_or_yaml_dump({"op1": "from default config file"}))
assert "from default config file" == parser.parse_args([]).op1

Check warning on line 490 in jsonargparse_tests/test_core.py

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Swap these 2 sides so they are in the correct order: actual value, expected value.

See more on https://sonarcloud.io/project/issues?id=omni-us_jsonargparse&issues=AZ9kdIKdOrCLrsKFihzy&open=AZ9kdIKdOrCLrsKFihzy&pullRequest=928
env = {"APP_CFG": str(input1_config_file)}
with subtests.test("parse_args environment config file"), patch.dict(os.environ, env):
assert "from input config file" == parser.parse_args([]).op1
Expand All @@ -497,7 +496,7 @@
assert "from env var" == parser.parse_args([]).op1
env["APP_CFG"] = str(input2_config_file)
with subtests.test("parse_args input argument"), patch.dict(os.environ, env):
assert "from arg" == parser.parse_args(["--op1", "from arg"]).op1

Check warning on line 499 in jsonargparse_tests/test_core.py

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Swap these 2 sides so they are in the correct order: actual value, expected value.

See more on https://sonarcloud.io/project/issues?id=omni-us_jsonargparse&issues=AZ9kdIKdOrCLrsKFihz1&open=AZ9kdIKdOrCLrsKFihz1&pullRequest=928
with subtests.test("parse_args argument override config"), patch.dict(os.environ, env):
assert "from arg" == parser.parse_args([f"--cfg={input1_config_file}", "--op1=from arg"]).op1
with subtests.test("parse_args config override argument"), patch.dict(os.environ, env):
Expand Down Expand Up @@ -948,6 +947,10 @@
assert json_or_yaml_load(out) == {"x": 1}


class Subclass:
pass


def test_default_config_files(parser, subtests, tmp_cwd):
default_config_file = tmp_cwd / "defaults.yaml"
default_config_file.write_text(json_or_yaml_dump({"op1": "from default config file"}))
Expand All @@ -964,13 +967,13 @@

with subtests.test("get_defaults"):
cfg = parser.get_defaults()
assert "from default config file" == cfg.op1

Check warning on line 970 in jsonargparse_tests/test_core.py

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Swap these 2 sides so they are in the correct order: actual value, expected value.

See more on https://sonarcloud.io/project/issues?id=omni-us_jsonargparse&issues=AZ9kdIKdOrCLrsKFihz5&open=AZ9kdIKdOrCLrsKFihz5&pullRequest=928
assert "from parser default" == cfg.op2

with subtests.test("get_default"):
assert parser.get_default("op1") == "from default config file"
parser.add_subclass_arguments(Calendar, "cal")
assert parser.get_default("cal") is None
parser.add_subclass_arguments(Subclass, "cls")
assert parser.get_default("cls") is None

with subtests.test("set invalid"):
with pytest.raises(ValueError) as ctx:
Expand Down
137 changes: 70 additions & 67 deletions jsonargparse_tests/test_link_arguments.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
from __future__ import annotations

import json
from calendar import Calendar, TextCalendar
from dataclasses import dataclass
from importlib.util import find_spec
from typing import Any, Callable, List, Mapping, Optional, Union

import pytest
Expand Down Expand Up @@ -89,34 +87,39 @@
ctx.match('Parser key "b.v2"')


class BaseC:
def __init__(self, p: int = 0):
self.p = p


class SubC(BaseC):
pass


def test_on_parse_compute_fn_subclass_spec(parser, subtests):
parser.add_argument("--cfg", action="config")
parser.add_argument("--cal1", type=Calendar, default=lazy_instance(TextCalendar))
parser.add_argument("--cal2", type=Calendar, default=lazy_instance(Calendar))
parser.add_argument("--obj1", type=BaseC, default=lazy_instance(SubC))
parser.add_argument("--obj2", type=BaseC, default=lazy_instance(BaseC))
parser.link_arguments(
"cal1",
"cal2.init_args.firstweekday",
compute_fn=lambda c: c.init_args.firstweekday + 1,
"obj1",
"obj2.init_args.p",
compute_fn=lambda c: c.init_args.p + 1,
)

with subtests.test("from config and default init_args"):
cfg = parser.parse_args(['--cfg={"cal1": "Calendar"}'])
assert cfg.cal1.init_args.firstweekday == 0
assert cfg.cal2.init_args.firstweekday == 1
cfg = parser.parse_args(['--cfg={"obj1": "BaseC"}'])
assert cfg.obj1.init_args.p == 0
assert cfg.obj2.init_args.p == 1

with subtests.test("from given init_args"):
cfg = parser.parse_args(["--cal1.init_args.firstweekday=2"])
assert cfg.cal1.init_args.firstweekday == 2
assert cfg.cal2.init_args.firstweekday == 3
cfg = parser.parse_args(["--obj1.init_args.p=2"])
assert cfg.obj1.init_args.p == 2
assert cfg.obj2.init_args.p == 3

with subtests.test("invalid init parameter"):
parser.set_defaults(cal1=None)
with pytest.raises(ArgumentError) as ctx:
parser.parse_args(["--cal1.firstweekday=-"])
if find_spec("typeshed_client"):
ctx.match('Parser key "cal1"')
else:
ctx.match("Call to compute_fn of link")
parser.set_defaults(obj1=None)
with pytest.raises(ArgumentError, match='Parser key "obj1"'):
parser.parse_args(["--obj1.p=-"])


class ClassA:
Expand Down Expand Up @@ -156,7 +159,7 @@

with subtests.test("dump removal of targets"):
cfg = parser.parse_args(["--a.v1=11", "--a.v2=7"])
assert 7 == cfg.b.v1

Check warning on line 162 in jsonargparse_tests/test_link_arguments.py

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Swap these 2 sides so they are in the correct order: actual value, expected value.

See more on https://sonarcloud.io/project/issues?id=omni-us_jsonargparse&issues=AZ9kdIK1OrCLrsKFihz-&open=AZ9kdIK1OrCLrsKFihz-&pullRequest=928
assert 11 + 7 == cfg.b.v2
dump = json_or_yaml_load(parser.dump(cfg))
assert dump == {"a": {"v1": 11, "v2": 7}, "b": {"v3": 2}}
Expand Down Expand Up @@ -312,46 +315,46 @@
def __init__(
self,
v: Union[int, str] = 1,
c: Optional[Calendar] = None,
c: Optional[BaseC] = None,
):
self.c = c


def test_on_parse_add_subclass_arguments_with_instantiate_false(parser, subtests):
parser.add_subclass_arguments(ClassF, "f")
parser.add_subclass_arguments(Calendar, "c", instantiate=False)
parser.add_subclass_arguments(BaseC, "c", instantiate=False)
parser.link_arguments("c", "f.init_args.c")

f_value = {"class_path": f"{__name__}.ClassF"}
c_value = {
"class_path": "calendar.Calendar",
"class_path": f"{__name__}.BaseC",
"init_args": {
"firstweekday": 3,
"p": 3,
},
}

with subtests.test("parse_args"):
cfg = parser.parse_args([f"--f={json.dumps(f_value)}", f"--c={json.dumps(c_value)}"])
assert cfg.c.as_dict() == {
"class_path": "calendar.Calendar",
"init_args": {"firstweekday": 3},
"class_path": f"{__name__}.BaseC",
"init_args": {"p": 3},
}
assert cfg.c == cfg.f.init_args.c

with subtests.test("class instantiation"):
init = parser.instantiate(cfg)
assert isinstance(init.c, Namespace)
assert isinstance(init.f, ClassF)
assert isinstance(init.f.c, Calendar)
assert init.f.c.firstweekday == 3
assert isinstance(init.f.c, BaseC)
assert init.f.c.p == 3

with subtests.test("dump removal of target"):
dump = json_or_yaml_load(parser.dump(cfg))
assert "c" not in dump["f"]["init_args"]

with subtests.test("dump keep target"):
dump = json_or_yaml_load(parser.dump(cfg, skip_link_targets=False))
assert dump["f"]["init_args"]["c"] == {"class_path": "calendar.Calendar", "init_args": {"firstweekday": 3}}
assert dump["f"]["init_args"]["c"] == {"class_path": f"{__name__}.BaseC", "init_args": {"p": 3}}


class ClassD:
Expand All @@ -369,16 +372,16 @@
return value

parser.add_subclass_arguments(ClassD, "d")
parser.add_subclass_arguments(Calendar, "c")
parser.add_subclass_arguments(BaseC, "c")
parser.link_arguments("c", "d.init_args.a1", compute_fn=return_dict)
parser.link_arguments("c", "d.init_args.a2")
parser.link_arguments("c", "d.init_args.a3")

d_value = {"class_path": f"{__name__}.ClassD"}
c_value = {
"class_path": "calendar.Calendar",
"class_path": f"{__name__}.BaseC",
"init_args": {
"firstweekday": 3,
"p": 3,
},
}

Expand All @@ -388,7 +391,7 @@

init = parser.instantiate(cfg)
assert isinstance(init.d, ClassD)
assert isinstance(init.c, Calendar)
assert isinstance(init.c, BaseC)


def test_on_parse_add_subclass_help_group_title(parser):
Expand Down Expand Up @@ -636,78 +639,78 @@


class ClassN:
def __init__(self, calendar: Calendar):
self.calendar = calendar # pragma: no cover
def __init__(self, obj: BaseC):
self.obj = obj # pragma: no cover


def test_on_parse_and_instantiate_link_entire_instance(parser):
parser.add_argument("--firstweekday", type=int)
parser.add_argument("--p", type=int)
parser.add_class_arguments(ClassN, "n", instantiate=False)
parser.add_class_arguments(Calendar, "c")
parser.link_arguments("firstweekday", "c.firstweekday", apply_on="parse")
parser.link_arguments("c", "n.calendar", apply_on="instantiate")
parser.add_class_arguments(BaseC, "c")
parser.link_arguments("p", "c.p", apply_on="parse")
parser.link_arguments("c", "n.obj", apply_on="instantiate")

cfg = parser.parse_args(["--firstweekday=2"])
assert cfg == Namespace(c=Namespace(firstweekday=2), firstweekday=2)
cfg = parser.parse_args(["--p=2"])
assert cfg == Namespace(c=Namespace(p=2), p=2)
init = parser.instantiate(cfg)
assert isinstance(init.n, Namespace)
assert isinstance(init.c, Calendar)
assert init.c is init.n.calendar
assert isinstance(init.c, BaseC)
assert init.c is init.n.obj


class ClassM:
def __init__(self, calendars: List[Calendar]):
self.calendars = calendars
def __init__(self, objs: List[BaseC]):
self.objs = objs


def test_on_instantiate_link_multi_source(parser):
def as_list(*items):
return [*items]

parser.add_class_arguments(ClassM, "m")
parser.add_class_arguments(Calendar, "c.one")
parser.add_class_arguments(TextCalendar, "c.two")
parser.link_arguments(("c.one", "c.two"), "m.calendars", apply_on="instantiate", compute_fn=as_list)
parser.add_class_arguments(BaseC, "c.one")
parser.add_class_arguments(SubC, "c.two")
parser.link_arguments(("c.one", "c.two"), "m.objs", apply_on="instantiate", compute_fn=as_list)

cfg = parser.parse_args([])
assert cfg.as_dict() == {"c": {"one": {"firstweekday": 0}, "two": {"firstweekday": 0}}}
assert cfg.as_dict() == {"c": {"one": {"p": 0}, "two": {"p": 0}}}
init = parser.instantiate(cfg)
assert isinstance(init.c.one, Calendar)
assert isinstance(init.c.two, TextCalendar)
assert init.m.calendars == [init.c.one, init.c.two]
assert isinstance(init.c.one, BaseC)
assert isinstance(init.c.two, SubC)
assert init.m.objs == [init.c.one, init.c.two]


class ClassP:
def __init__(self, firstweekday: int = 1):
self.calendar = Calendar(firstweekday=firstweekday)
def __init__(self, p: int = 1):
self.obj = BaseC(p=p)


class ClassQ:
def __init__(self, calendar: Calendar, q2: int = 2):
self.calendar = calendar
def __init__(self, obj: BaseC, q2: int = 2):
self.obj = obj


def test_on_instantiate_link_object_in_attribute(parser):
parser.add_class_arguments(ClassP, "p")
parser.add_class_arguments(ClassQ, "q")
parser.link_arguments("p.calendar", "q.calendar", apply_on="instantiate")
parser.link_arguments("p.obj", "q.obj", apply_on="instantiate")

cfg = parser.parse_args(["--p.firstweekday=2", "--q.q2=3"])
assert cfg.p == Namespace(firstweekday=2)
cfg = parser.parse_args(["--p.p=2", "--q.q2=3"])
assert cfg.p == Namespace(p=2)
assert cfg.q == Namespace(q2=3)
init = parser.instantiate(cfg)
assert init.p.calendar is init.q.calendar
assert init.q.calendar.firstweekday == 2
assert init.p.obj is init.q.obj
assert init.q.obj.p == 2


def test_on_parse_link_entire_subclass(parser):
parser.add_class_arguments(ClassN, "n")
parser.add_class_arguments(ClassQ, "q")
parser.link_arguments("n.calendar", "q.calendar", apply_on="parse")
parser.link_arguments("n.obj", "q.obj", apply_on="parse")

cal = {"class_path": "Calendar", "init_args": {"firstweekday": 4}}
cfg = parser.parse_args([f"--n.calendar={json.dumps(cal)}", "--q.q2=7"])
assert cfg.n.calendar == cfg.q.calendar
obj = {"class_path": f"{__name__}.BaseC", "init_args": {"p": 4}}
cfg = parser.parse_args([f"--n.obj={json.dumps(obj)}", "--q.q2=7"])
assert cfg.n.obj == cfg.q.obj
assert cfg.q.q2 == 7


Expand Down Expand Up @@ -1086,8 +1089,8 @@
parser.add_class_arguments(ClassP, "p")
parser.add_class_arguments(ClassQ, "q")
with pytest.raises(ValueError) as ctx:
parser.link_arguments("p.calendar", "q.calendar", apply_on="parse")
ctx.match('key "p.calendar"')
parser.link_arguments("p.obj", "q.obj", apply_on="parse")
ctx.match('key "p.obj"')


def test_on_instantiate_link_failure_cycle_self():
Expand Down
28 changes: 18 additions & 10 deletions jsonargparse_tests/test_parameter_resolvers.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
import calendar
import inspect
import xml.dom
from calendar import Calendar
from random import shuffle
from typing import Any, Callable, Dict, List, Optional, Union
from unittest.mock import patch
Expand Down Expand Up @@ -361,19 +360,28 @@
self.lr = lr


class BaseC:
def __init__(self, p: int = 0):
self.p = p


class SubC(BaseC):
pass


class ClassInstanceDefaults:
def __init__(
self,
layers: int,
number: float = 0.2,
elements: Callable[..., List[int]] = lambda *a: list(a),
node: Callable[[], xml.dom.Node] = lambda: xml.dom.Node(),
cal1: Calendar = Calendar(firstweekday=1),
cal2: Calendar = calendar.TextCalendar(2),
inst1: BaseC = BaseC(p=1),
inst2: BaseC = SubC(2),
opt1: Callable[[List[int]], Optimizer] = lambda p: SGD(p, lr=0.01),
opt2: Callable[[List[int]], Optimizer] = lambda p: SGD(p, 0.02),
opt3: Callable[[List[int]], Optimizer] = lambda p, lr=0.1: SGD(p, lr=lr), # type: ignore[misc]
opt4: Callable[[List[int]], Optimizer] = lambda p: Calendar(firstweekday=3), # type: ignore[assignment,return-value]
opt4: Callable[[List[int]], Optimizer] = lambda p: BaseC(p=3), # type: ignore[assignment,return-value]
**kwargs,
):
"""
Expand All @@ -382,8 +390,8 @@
number: help for number
elements: help for elements
node: help for node
cal1: help for cal1
cal2: help for cal2
inst1: help for inst1
inst2: help for inst2
opt1: help for opt1
opt2: help for opt2
opt3: help for opt3
Expand Down Expand Up @@ -708,8 +716,8 @@
"number",
"elements",
"node",
"cal1",
"cal2",
"inst1",
"inst2",
"opt1",
"opt2",
"opt3",
Expand All @@ -722,10 +730,10 @@
assert is_lambda(params[2].default)
with subtests.test("supported defaults"):
assert params[3].default == dict(class_path="xml.dom.Node")
assert params[4].default == dict(class_path="calendar.Calendar", init_args=dict(firstweekday=1))
assert params[4].default == dict(class_path=f"{__name__}.BaseC", init_args=dict(p=1))
assert params[6].default == dict(class_path=f"{__name__}.SGD", init_args=dict(lr=0.01))
with subtests.test("unsupported defaults"):
assert isinstance(params[5].default, calendar.TextCalendar)
assert isinstance(params[5].default, SubC)
assert is_lambda(params[7].default)
assert is_lambda(params[9].default)
with subtests.test("invalid defaults"):
Expand Down Expand Up @@ -835,12 +843,12 @@

def test_get_params_function_module_class():
params = get_params(function_module_class)
assert ["firstweekday"] == [p.name for p in params]

Check warning on line 846 in jsonargparse_tests/test_parameter_resolvers.py

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Swap these 2 sides so they are in the correct order: actual value, expected value.

See more on https://sonarcloud.io/project/issues?id=omni-us_jsonargparse&issues=AZ9kdIOSOrCLrsKFih1p&open=AZ9kdIOSOrCLrsKFih1p&pullRequest=928


def test_get_params_function_local_import():
params = get_params(function_local_import)
assert ["mode", "loader_fn", "exceptions", "json_superset"] == [p.name for p in params]

Check warning on line 851 in jsonargparse_tests/test_parameter_resolvers.py

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Swap these 2 sides so they are in the correct order: actual value, expected value.

See more on https://sonarcloud.io/project/issues?id=omni-us_jsonargparse&issues=AZ9kdIOSOrCLrsKFih1q&open=AZ9kdIOSOrCLrsKFih1q&pullRequest=928


def function_nested_module_attr(**kwargs): # pragma: no cover
Expand All @@ -867,7 +875,7 @@
params = get_params(ClassNestedModuleAttr)
assert "string" in [p.name for p in params]
with source_unavailable():
assert [] == get_params(ClassNestedModuleAttr)

Check warning on line 878 in jsonargparse_tests/test_parameter_resolvers.py

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Swap these 2 sides so they are in the correct order: actual value, expected value.

See more on https://sonarcloud.io/project/issues?id=omni-us_jsonargparse&issues=AZ9kdIOSOrCLrsKFih1s&open=AZ9kdIOSOrCLrsKFih1s&pullRequest=928


class ClassNestedLocalFromImport:
Expand Down
Loading