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 @@ -261,6 +261,10 @@ Changed
describes the accepted values, a list given for ``Hashable`` becomes a tuple,
and ``Sized`` rejects subclass specs (`#1007
<https://github.com/mauvilsa/jsonargparse/pull/1007>`__).
- YAML values that look like base 60 numbers, e.g. ``10:30``, are now loaded as
strings, as in YAML 1.2, instead of as numbers, which prevented parsing them
as ``datetime.time`` (`#1008
<https://github.com/mauvilsa/jsonargparse/pull/1008>`__).

Removed
^^^^^^^
Expand Down
5 changes: 3 additions & 2 deletions DOCUMENTATION.rst
Original file line number Diff line number Diff line change
Expand Up @@ -1360,8 +1360,9 @@ jsonargparse can parse configuration files (config files). The dot notation
hierarchy of the arguments (see :ref:`nested-namespaces`) defines the structure
expected in these files. The default ``parser_mode`` is ``json_or_yaml``, which
parses as JSON and, if that fails, as YAML. YAML requires the ``yaml`` extra,
see :ref:`installation`, so without it only JSON is accepted. To change the
mode, use the ``parser_mode`` parameter of the parser, e.g.
see :ref:`installation`, so without it only JSON is accepted. As in YAML 1.2,
timestamps and base 60 numbers, e.g. ``10:30``, are loaded as strings. To change
the mode, use the ``parser_mode`` parameter of the parser, e.g.
``ArgumentParser(parser_mode="json")``.

The :py:attr:`.ArgumentParser.default_config_files` property holds patterns of
Expand Down
15 changes: 14 additions & 1 deletion jsonargparse/_loaders_dumpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -79,16 +79,29 @@
]

remove_implicit_resolver(DefaultLoader, "tag:yaml.org,2002:timestamp")
remove_implicit_resolver(DefaultLoader, "tag:yaml.org,2002:int")
remove_implicit_resolver(DefaultLoader, "tag:yaml.org,2002:float")

# Without YAML 1.1 sexagesimal (base 60) numbers, e.g. 10:30, which YAML 1.2 removed
DefaultLoader.add_implicit_resolver(
"tag:yaml.org,2002:int",
re.compile(
"""^(?:

Check warning on line 89 in jsonargparse/_loaders_dumpers.py

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Simplify this regular expression to reduce its complexity from 31 to the 20 allowed.

See more on https://sonarcloud.io/project/issues?id=mauvilsa_jsonargparse&issues=AaEaP6azrIqpKK-Murij&open=AaEaP6azrIqpKK-Murij&pullRequest=1008
[-+]?0b[0-1_]+
|[-+]?0[0-7_]+
|[-+]?(?:0|[1-9][0-9_]*)
|[-+]?0x[0-9a-fA-F_]+)$""",
re.X,
),
list("-+0123456789"),
)
DefaultLoader.add_implicit_resolver(
"tag:yaml.org,2002:float",
re.compile(
"""^(?:
[-+]?(?:[0-9][0-9_]*)\\.[0-9_]*(?:[eE][-+]?[0-9]+)?
|[-+]?(?:[0-9][0-9_]*)(?:[eE][-+]?[0-9]+)
|\\.[0-9_]+(?:[eE][-+][0-9]+)?
|[-+]?[0-9][0-9_]*(?::[0-5]?[0-9])+\\.[0-9_]*
|[-+]?\\.(?:inf|Inf|INF)
|\\.(?:nan|NaN|NAN))$""",
re.X,
Expand Down
16 changes: 15 additions & 1 deletion jsonargparse_tests/test_loaders_dumpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
import sys
from dataclasses import dataclass
from pathlib import Path
from typing import List
from typing import List, Union
from unittest.mock import patch

import pytest
Expand Down Expand Up @@ -56,6 +56,20 @@ def test_yaml_implicit_null_disabled(parser):
assert "Foo:" == cfg.bar.init_args.x


@skip_if_no_pyyaml
@pytest.mark.parametrize("value", ["10:30", "-1:30:00", "10:30:00.5"])
def test_yaml_implicit_sexagesimal_disabled(parser, value):
parser.add_argument("--val", type=Union[int, float, str])
assert value == parser.parse_args([f"--val={value}"]).val


@skip_if_no_pyyaml
@pytest.mark.parametrize(["value", "expected"], [("-1_000", -1000), ("0x1f", 31), ("1.5e3", 1500.0), (".5", 0.5)])
def test_yaml_implicit_numbers(parser, value, expected):
parser.add_argument("--val", type=Union[int, float, str])
assert expected == parser.parse_args([f"--val={value}"]).val


def test_invalid_parser_mode():
pytest.raises(ValueError, lambda: ArgumentParser(parser_mode="invalid"))

Expand Down
12 changes: 11 additions & 1 deletion jsonargparse_tests/test_typing.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@
restricted_number_type,
restricted_string_type,
)
from jsonargparse_tests.conftest import capture_logs, get_parser_help, json_or_yaml_load
from jsonargparse_tests.conftest import capture_logs, get_parser_help, json_or_yaml_load, skip_if_no_pyyaml

if sys.version_info >= (3, 12):
from typing import TypeAliasType
Expand Down Expand Up @@ -580,6 +580,16 @@ def test_datetime_types(parser, type_, value, expected):
parser.parse_args(["--value=not a date"])


@skip_if_no_pyyaml
def test_time_in_list_and_config(parser):
parser.add_argument("--times", type=List[time])
parser.add_argument("--opt", type=Optional[time])
cfg = parser.parse_args(["--times=[10:30, 11:00:30]"])
assert cfg.times == [time(10, 30), time(11, 0, 30)]
cfg = parser.parse_string("opt: 10:30")
assert cfg.opt == time(10, 30)


def test_datetime_rejects_date(parser):
parser.add_argument("--date", type=date)
with pytest.raises(ArgumentError, match="--date"):
Expand Down
Loading