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
5 changes: 5 additions & 0 deletions CHANGELOG.rst
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,11 @@ paths are considered internals and can change in minor and patch releases.
v4.49.0 (unreleased)
--------------------

Added
^^^^^
- Support ``Deque`` and ``FrozenSet`` in type hints (`#905
<https://github.com/omni-us/jsonargparse/pull/905>`__).

Changed
^^^^^^^
- Docs now reference methods via the public ``ArgumentParser`` class instead of
Expand Down
22 changes: 11 additions & 11 deletions DOCUMENTATION.rst
Original file line number Diff line number Diff line change
Expand Up @@ -455,11 +455,11 @@ Some notes about this support are:
- Fully supported types are: ``str``, ``bool`` (more details in
:ref:`boolean-arguments`), ``int``, ``float``, ``Decimal``, ``complex``,
``bytes``/``bytearray`` (Base64 encoding), ``range``, ``list`` (more details
in :ref:`list-append`), ``Iterable``, ``Sequence``, ``Any``, ``Union``,
``Optional``, ``Type``, ``Enum``, ``PathLike``, ``UUID``, ``timedelta``,
restricted types as explained in sections :ref:`restricted-numbers` and
:ref:`restricted-strings` and path and URL types as explained in sections
:ref:`parsing-paths` and :ref:`parsing-urls`.
in :ref:`list-append`), ``Deque``, ``Iterable``, ``Sequence``, ``Any``,
``Union``, ``Optional``, ``Type``, ``Enum``, ``PathLike``, ``UUID``,
``timedelta``, restricted types as explained in sections
:ref:`restricted-numbers` and :ref:`restricted-strings` and path and URL types
as explained in sections :ref:`parsing-paths` and :ref:`parsing-urls`.

- ``dict``, ``Mapping``, ``MutableMapping``, ``MappingProxyType``,
``OrderedDict``, and ``TypedDict`` are supported but only with ``str`` or
Expand All @@ -469,12 +469,12 @@ Some notes about this support are:
typing as described in PEP `692 <https://peps.python.org/pep-0692/>`__.
For more details see :ref:`dict-items`.

- ``tuple``, ``set`` and ``MutableSet`` are supported even though they can't be
represented in JSON distinguishable from a list. Each ``tuple`` element
position can have its own type and will be validated as such. ``tuple`` with
ellipsis (``tuple[type, ...]``) is also supported. In command line arguments,
config files and environment variables, tuples and sets are represented as an
array.
- ``tuple``, ``set``, ``frozenset`` and ``MutableSet`` are supported even though
they can't be represented in JSON distinguishable from a list. Each ``tuple``
element position can have its own type and will be validated as such.
``tuple`` with ellipsis (``tuple[type, ...]``) is also supported. In command
line arguments, config files and environment variables, tuples and sets are
represented as an array.

- To set a value to ``None`` it is required to use ``null`` since this is how
JSON/YAML defines it. To avoid confusion in the help, ``NoneType`` is
Expand Down
21 changes: 18 additions & 3 deletions jsonargparse/_typehints.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
import re
import sys
from argparse import ArgumentError
from collections import OrderedDict, abc, defaultdict
from collections import OrderedDict, abc, defaultdict, deque
from contextlib import contextmanager, suppress
from contextvars import ContextVar
from copy import deepcopy
Expand All @@ -16,8 +16,10 @@
from typing import (
Any,
Callable,
Deque,
Dict,
ForwardRef,
FrozenSet,
Iterable,
List,
Literal,
Expand Down Expand Up @@ -119,6 +121,9 @@
Union,
List,
list,
FrozenSet,

Check warning on line 124 in jsonargparse/_typehints.py

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Change or remove duplicates of this key.

See more on https://sonarcloud.io/project/issues?id=omni-us_jsonargparse&issues=AZ3aqPHBNKelB9URLP5I&open=AZ3aqPHBNKelB9URLP5I&pullRequest=905
Deque,
deque,
Iterable,
Sequence,
MutableSequence,
Expand All @@ -128,6 +133,7 @@
Tuple,
tuple,
Set,
FrozenSet,
set,
frozenset,
MutableSet,
Expand Down Expand Up @@ -160,6 +166,8 @@
sequence_origin_types = {
List,
list,
Deque,
deque,
Iterable,
Sequence,
MutableSequence,
Expand Down Expand Up @@ -899,7 +907,7 @@

# Tuple or Set
elif typehint_origin in tuple_set_origin_types:
if not isinstance(val, (list, tuple, set)):
if not isinstance(val, (list, tuple, set, frozenset)):
raise_unexpected_value(f"Expected a {typehint_origin}", val)
val = list(val)
if subtypehints is not None:
Expand All @@ -911,7 +919,12 @@
subtypehint = subtypehints[0 if is_ellipsis or not is_tuple else n]
val[n] = adapt_typehints(v, subtypehint, **adapt_kwargs)
if not serialize:
val = tuple(val) if typehint_origin in {Tuple, tuple} else set(val)
if typehint_origin in {Tuple, tuple}:
val = tuple(val)
elif typehint_origin is frozenset:
val = frozenset(val)
else:
val = set(val)

# List, Iterable or Sequence
elif typehint_origin in sequence_origin_types:
Expand Down Expand Up @@ -950,6 +963,8 @@
adapt_kwargs_n = deepcopy(adapt_kwargs)
with change_to_path_dir(list_path):
val[n] = adapt_typehints(v, subtypehints[0], **adapt_kwargs_n)
if typehint_origin is deque:
val = list(val) if serialize else deque(val)

# Dict, Mapping
elif typehint_origin in mapping_origin_types:
Expand Down
22 changes: 21 additions & 1 deletion jsonargparse_tests/test_typehints.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,17 @@
import time
import uuid
from calendar import Calendar, TextCalendar
from collections import OrderedDict
from collections import OrderedDict, deque
from dataclasses import dataclass, field
from enum import Enum
from pathlib import Path
from types import MappingProxyType
from typing import (
Any,
Callable,
Deque,
Dict,
FrozenSet,
Iterable,
List,
Literal,
Expand Down Expand Up @@ -312,6 +314,16 @@ def test_set(parser):
ctx.match("Expected a <class 'int'>")


def test_frozenset(parser):
parser.add_argument("--frozen", type=FrozenSet[int])
cfg = parser.parse_args(["--frozen=[1, 2]"])
assert frozenset([1, 2]) == cfg.frozen
assert parser.dump(cfg, format="json") == '{"frozen":[1,2]}'
with pytest.raises(ArgumentError) as ctx:
parser.parse_args(['--frozen=["a", "b"]'])
ctx.match("Expected a <class 'int'>")


# tuple tests


Expand Down Expand Up @@ -373,6 +385,14 @@ def test_list_variants(parser, list_type):
assert [1, 2] == cfg.list


def test_deque(parser):
parser.add_argument("--deque", type=Deque[int])
cfg = parser.parse_args(["--deque=[1, 2]"])
assert isinstance(cfg.deque, deque)
assert deque([1, 2]) == cfg.deque
assert parser.dump(cfg, format="json") == '{"deque":[1,2]}'


def test_list_dump(parser):
parser.add_argument("--list", type=Union[PositiveInt, List[PositiveInt]])
dump = json_or_yaml_load(parser.dump(Namespace(list=[1, 2])))
Expand Down
Loading