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
1 change: 0 additions & 1 deletion .github/workflows/manual.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@ jobs:
- uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
with:
python-version: |
3.9
3.10
3.11
3.12
Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/tests.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ jobs:
runs-on: ubuntu-latest
strategy:
matrix:
python: ["3.9", "3.10", "3.11", "3.12", "3.13", "3.14"]
python: ["3.10", "3.11", "3.12", "3.13", "3.14"]
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
Expand Down Expand Up @@ -267,7 +267,7 @@ jobs:
-Dsonar.exclusions=sphinx/**
-Dsonar.tests=jsonargparse_tests
-Dsonar.python.coverage.reportPaths=coverage_*.xml
-Dsonar.python.version=3.9,3.10,3.11,3.12,3.13,3.14
-Dsonar.python.version=3.10,3.11,3.12,3.13,3.14
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # Needed to get PR information, if any
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
Expand Down
9 changes: 9 additions & 0 deletions CHANGELOG.rst
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,15 @@ The semantic versioning only considers the public API as described in
paths are considered internals and can change in minor and patch releases.


v4.50.0 (unreleased)
--------------------

Changed
^^^^^^^
- Drop support for Python 3.9. The minimum supported Python version is now
3.10 (`#916 <https://github.com/omni-us/jsonargparse/pull/916>`__).


v4.49.0 (2026-05-15)
--------------------

Expand Down
12 changes: 6 additions & 6 deletions DOCUMENTATION.rst
Original file line number Diff line number Diff line change
Expand Up @@ -443,9 +443,11 @@ Some notes about this support are:
limit in nesting depth.

- Postponed evaluation of types PEP `563 <https://peps.python.org/pep-0563/>`__
(i.e. ``from __future__ import annotations``) is supported. Also supported on
``python==3.9`` is PEP `604 <https://peps.python.org/pep-0604/>`__ (i.e.
``<type> | <type>`` instead of ``Union[<type>, <type>]``).
(i.e. ``from __future__ import annotations``) is supported. Also supported are
PEP `585 <https://peps.python.org/pep-0585/>`__ (i.e. ``list[<type>],
dict[<type>], ...`` instead of ``List[<type>], Dict[<type>], ...``) and `604
<https://peps.python.org/pep-0604/>`__ (i.e. ``<type> | <type>`` instead of
``Union[<type>, <type>]``).

- Types that use components imported inside ``TYPE_CHECKING`` blocks are
supported.
Expand Down Expand Up @@ -1875,9 +1877,7 @@ jsonargparse with the ``signatures`` extra as explained in section

Many of the types defined in stub files use the latest syntax for type hints,
that is, bitwise or operator ``|`` for unions, see PEP `604
<https://peps.python.org/pep-0604>`__. On ``python>=3.10`` these are fully
supported. On ``python<=3.9`` backporting these types is attempted and in some
cases it can fail. On failure the type annotation is set to ``Any``.
<https://peps.python.org/pep-0604>`__. This syntax is fully supported.

Most of the types in the Python standard library have their types in stubs. An
example from the standard library would be:
Expand Down
8 changes: 4 additions & 4 deletions jsonargparse/_actions.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
from argparse import Action as ArgparseAction
from contextlib import contextmanager
from contextvars import ContextVar
from typing import Any, Optional
from typing import Any

from ._common import Action, NonParsingAction, is_subclass, is_subclasses_disabled, parser_context
from ._loaders_dumpers import get_loader_exceptions, load_value
Expand Down Expand Up @@ -116,7 +116,7 @@ def apply_config(parser, cfg, dest, value) -> None:
with parser_context(single_subcommand=False), previous_config_context(cfg), skip_apply_links():
kwargs = {"env": False, "defaults": False, "_skip_validation": True, "_fail_no_subcommand": False}
try:
cfg_path: Optional[Path] = Path(value, mode=_get_config_read_mode())
cfg_path: Path | None = Path(value, mode=_get_config_read_mode())
except TypeError as ex_path:
try:
if isinstance(load_value(value), str):
Expand Down Expand Up @@ -224,7 +224,7 @@ def is_print_config_requested(parser):


class _ActionConfigLoad(Action):
def __init__(self, basetype: Optional[type] = None, **kwargs):
def __init__(self, basetype: type | None = None, **kwargs):
if len(kwargs) == 0:
self._basetype = basetype
else:
Expand Down Expand Up @@ -267,7 +267,7 @@ class _ActionHelpClassPath(NonParsingAction):
sub_add_kwargs: dict[str, Any] = {}

@classmethod
def get_help_types(cls, typehint) -> Optional[tuple]:
def get_help_types(cls, typehint) -> tuple | None:
from ._typehints import get_subclass_or_closed_types

return get_subclass_or_closed_types(typehint=typehint, also_lists=True, callable_return=True)
Expand Down
7 changes: 4 additions & 3 deletions jsonargparse/_cli.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
"""Simple creation of command line interfaces."""

import inspect
from typing import Any, Callable, Optional, Union
from collections.abc import Callable
from typing import Any, Optional, Union

from ._actions import ActionConfigFile, _ActionPrintConfig, remove_actions
from ._core import ArgumentParser
Expand All @@ -25,9 +26,9 @@ def CLI(*args, **kwargs):

def auto_cli(
components: ComponentsType = None,
args: Optional[list[str]] = None,
args: list[str] | None = None,
config_help: str = default_config_option_help,
set_defaults: Optional[dict[str, Any]] = None,
set_defaults: dict[str, Any] | None = None,
as_positional: bool = True,
return_instance: bool = False,
fail_untyped: bool = True,
Expand Down
47 changes: 23 additions & 24 deletions jsonargparse/_common.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,14 @@
import inspect
import logging
import os
from collections.abc import Callable
from contextlib import contextmanager
from contextvars import ContextVar
from typing import ( # type: ignore[attr-defined]
Callable,
Generic,
Optional,
Protocol,
TypeVar,
Union,
_GenericAlias,
)

Expand Down Expand Up @@ -52,17 +51,17 @@ def __call__(self, class_type: type[ClassType], *args, **kwargs) -> ClassType:
InstantiatorsDictType = dict[tuple[type, bool], InstantiatorCallable]


parent_parser: ContextVar[Optional[ArgumentParser]] = ContextVar("parent_parser", default=None)
parent_parser: ContextVar[ArgumentParser | None] = ContextVar("parent_parser", default=None)
parser_capture: ContextVar[bool] = ContextVar("parser_capture", default=False)
defaults_cache: ContextVar[Optional[Namespace]] = ContextVar("defaults_cache", default=None)
lenient_check: ContextVar[Union[bool, str]] = ContextVar("lenient_check", default=False)
defaults_cache: ContextVar[Namespace | None] = ContextVar("defaults_cache", default=None)
lenient_check: ContextVar[bool | str] = ContextVar("lenient_check", default=False)
parsing_defaults: ContextVar[bool] = ContextVar("parsing_defaults", default=False)
single_subcommand: ContextVar[bool] = ContextVar("single_subcommand", default=True)
validating_defaults: ContextVar[bool] = ContextVar("validating_defaults", default=False)
load_value_mode: ContextVar[Optional[str]] = ContextVar("load_value_mode", default=None)
class_instantiators: ContextVar[Optional[InstantiatorsDictType]] = ContextVar("class_instantiators", default=None)
load_value_mode: ContextVar[str | None] = ContextVar("load_value_mode", default=None)
class_instantiators: ContextVar[InstantiatorsDictType | None] = ContextVar("class_instantiators", default=None)
nested_links: ContextVar[list[dict]] = ContextVar("nested_links", default=[])
applied_instantiation_links: ContextVar[Optional[set]] = ContextVar("applied_instantiation_links", default=None)
applied_instantiation_links: ContextVar[set | None] = ContextVar("applied_instantiation_links", default=None)
path_dump_preserve_relative: ContextVar[bool] = ContextVar("path_dump_preserve_relative", default=False)


Expand Down Expand Up @@ -115,17 +114,17 @@ def get_env_var_bool(name: str) -> bool:

def set_parsing_settings(
*,
validate_defaults: Optional[bool] = None,
config_read_mode_urls_enabled: Optional[bool] = None,
config_read_mode_fsspec_enabled: Optional[bool] = None,
validate_defaults: bool | None = None,
config_read_mode_urls_enabled: bool | None = None,
config_read_mode_fsspec_enabled: bool | None = None,
docstring_parse_style: Optional["docstring_parser.DocstringStyle"] = None,
docstring_parse_attribute_docstrings: Optional[bool] = None,
parse_optionals_as_positionals: Optional[bool] = None,
add_print_completion_argument: Optional[bool] = None,
stubs_resolver_allow_py_files: Optional[bool] = None,
omegaconf_absolute_to_relative_paths: Optional[bool] = None,
subclasses_disabled: Optional[list[Union[type, Callable[[type], bool]]]] = None,
subclasses_enabled: Optional[list[Union[type, str]]] = None,
docstring_parse_attribute_docstrings: bool | None = None,
parse_optionals_as_positionals: bool | None = None,
add_print_completion_argument: bool | None = None,
stubs_resolver_allow_py_files: bool | None = None,
omegaconf_absolute_to_relative_paths: bool | None = None,
subclasses_disabled: list[type | Callable[[type], bool]] | None = None,
subclasses_enabled: list[type | str] | None = None,
) -> None:
"""
Modify global parser settings that affect parser creation and parsing behavior.
Expand Down Expand Up @@ -329,7 +328,7 @@ def is_pure_dataclass(cls) -> bool:

subclasses_enabled_types: set[type] = set()
subclasses_disabled_types: set[type] = set()
subclasses_disabled_selectors: dict[str, Callable[[type], Union[bool, int]]] = {
subclasses_disabled_selectors: dict[str, Callable[[type], bool | int]] = {
"is_pure_dataclass": is_pure_dataclass,
"is_pydantic_model": is_pydantic_model,
"is_attrs_class": is_attrs_class,
Expand All @@ -351,8 +350,8 @@ def is_subclasses_disabled(cls) -> bool:


def subclass_type_behavior(
subclasses_disabled: Optional[list[Union[type, Callable[[type], bool]]]] = None,
subclasses_enabled: Optional[list[Union[type, str]]] = None,
subclasses_disabled: list[type | Callable[[type], bool]] | None = None,
subclasses_enabled: list[type | str] | None = None,
) -> None:
"""Configures whether class types accept or not subclasses."""
for enable_item in subclasses_enabled or []:
Expand Down Expand Up @@ -404,7 +403,7 @@ def setup_default_logger(data, level, caller):
return logger


def parse_logger(logger: Union[bool, str, dict, logging.Logger], caller):
def parse_logger(logger: bool | str | dict | logging.Logger, caller):
if not isinstance(logger, (bool, str, dict, logging.Logger)):
raise ValueError(f"Expected logger to be an instance of (bool, str, dict, logging.Logger), but got {logger}.")
if isinstance(logger, dict) and len(set(logger) - {"name", "level"}) > 0:
Expand All @@ -425,7 +424,7 @@ def parse_logger(logger: Union[bool, str, dict, logging.Logger], caller):
class LoggerProperty:
"""Class designed to be inherited by other classes to add a logger property."""

def __init__(self, *args, logger: Union[bool, str, dict, logging.Logger] = False, **kwargs):
def __init__(self, *args, logger: bool | str | dict | logging.Logger = False, **kwargs):
self.logger = logger
super().__init__(*args, **kwargs)

Expand All @@ -444,7 +443,7 @@ def logger(self) -> logging.Logger:
return self._logger

@logger.setter
def logger(self, logger: Union[bool, str, dict, logging.Logger]):
def logger(self, logger: bool | str | dict | logging.Logger):
if logger is None:
from ._deprecated import deprecation_warning, logger_property_none_message

Expand Down
4 changes: 2 additions & 2 deletions jsonargparse/_completions.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
from enum import Enum
from importlib.util import find_spec
from subprocess import PIPE, Popen
from typing import Literal, Optional, Union
from typing import Literal, Union

from ._actions import ActionConfigFile, ActionFail, _ActionConfigLoad, _ActionHelpClassPath, remove_actions
from ._common import NonParsingAction, get_optionals_as_positionals_actions, get_parsing_setting
Expand Down Expand Up @@ -141,7 +141,7 @@ def get_completion_script(parser, completion_type: str, **kwargs) -> str:
return get_shtab_script(parser, completion_type[len("shtab-") :], **kwargs)


def get_shtab_script(parser, shell: str, preambles: Optional[list[str]] = None) -> str:
def get_shtab_script(parser, shell: str, preambles: list[str] | None = None) -> str:
import shtab

if shell not in shtab.SUPPORTED_SHELLS:
Expand Down
Loading
Loading