Skip to content

Commit aaa8cb2

Browse files
authored
Remove support for python 3.8 (#752)
1 parent 1be4976 commit aaa8cb2

18 files changed

+92
-205
lines changed

.github/workflows/manual.yaml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,6 @@ jobs:
2020
- uses: actions/setup-python@v5
2121
with:
2222
python-version: |
23-
3.8
2423
3.9
2524
3.10
2625
3.11

.github/workflows/tests.yaml

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ jobs:
1919
runs-on: ubuntu-latest
2020
strategy:
2121
matrix:
22-
python: [3.8, 3.9, "3.10", 3.11, 3.12, 3.13]
22+
python: ["3.9", "3.10", "3.11", "3.12", "3.13"]
2323
steps:
2424
- uses: actions/checkout@v4
2525
- uses: actions/setup-python@v5
@@ -59,7 +59,7 @@ jobs:
5959
strategy:
6060
fail-fast: false
6161
matrix:
62-
python: [3.9, "3.10", 3.11, 3.12, 3.13]
62+
python: ["3.9", "3.10", "3.11", "3.12", "3.13"]
6363
steps:
6464
- uses: actions/checkout@v4
6565
- uses: actions/setup-python@v5
@@ -74,7 +74,7 @@ jobs:
7474
strategy:
7575
fail-fast: false
7676
matrix:
77-
python: ["3.10", 3.12]
77+
python: ["3.10", "3.12"]
7878
steps:
7979
- uses: actions/checkout@v4
8080
- uses: actions/setup-python@v5
@@ -90,7 +90,7 @@ jobs:
9090
- uses: actions/checkout@v4
9191
- uses: actions/setup-python@v5
9292
with:
93-
python-version: "3.10"
93+
python-version: "3.12"
9494
cache: pip
9595
- run: pip install tox
9696
- run: tox -e omegaconf
@@ -101,7 +101,7 @@ jobs:
101101
- uses: actions/checkout@v4
102102
- uses: actions/setup-python@v5
103103
with:
104-
python-version: "3.10"
104+
python-version: "3.12"
105105
cache: pip
106106
- name: With pydantic<2
107107
run: |
@@ -134,7 +134,7 @@ jobs:
134134
- uses: actions/checkout@v4
135135
- uses: actions/setup-python@v5
136136
with:
137-
python-version: "3.10"
137+
python-version: "3.12"
138138
- name: Build package
139139
run: |
140140
pip install -U build
@@ -151,7 +151,7 @@ jobs:
151151
- uses: actions/checkout@v4
152152
- uses: actions/setup-python@v5
153153
with:
154-
python-version: "3.10"
154+
python-version: "3.12"
155155
cache: pip
156156
- uses: actions/download-artifact@v4
157157
with:
@@ -175,7 +175,7 @@ jobs:
175175
- uses: actions/checkout@v4
176176
- uses: actions/setup-python@v5
177177
with:
178-
python-version: "3.10"
178+
python-version: "3.12"
179179
cache: pip
180180
- run: pip install -e .[all,doc]
181181
- name: Run doc tests
@@ -249,7 +249,7 @@ jobs:
249249
-Dsonar.exclusions=sphinx/**
250250
-Dsonar.tests=jsonargparse_tests
251251
-Dsonar.python.coverage.reportPaths=coverage_*.xml
252-
-Dsonar.python.version=3.8,3.9,3.10,3.11,3.12,3.13
252+
-Dsonar.python.version=3.9,3.10,3.11,3.12,3.13
253253
env:
254254
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # Needed to get PR information, if any
255255
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}

CHANGELOG.rst

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,15 @@ The semantic versioning only considers the public API as described in
1212
paths are considered internals and can change in minor and patch releases.
1313

1414

15+
v4.41.0 (2025-08-??)
16+
--------------------
17+
18+
Changed
19+
^^^^^^^
20+
- Removed support for python 3.8 (`#752
21+
<https://github.com/omni-us/jsonargparse/pull/752>`__).
22+
23+
1524
v4.40.2 (2025-08-06)
1625
--------------------
1726

DOCUMENTATION.rst

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -428,10 +428,8 @@ Some notes about this support are:
428428

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

436434
- Types that use components imported inside ``TYPE_CHECKING`` blocks are
437435
supported.
@@ -1941,9 +1939,8 @@ jsonargparse with the ``signatures`` extras require as explained in section
19411939

19421940
Many of the types defined in stub files use the latest syntax for type hints,
19431941
that is, bitwise or operator ``|`` for unions and generics, e.g.
1944-
``list[<type>]`` instead of ``typing.List[<type>]``, see PEPs `604
1945-
<https://peps.python.org/pep-0604>`__ and `585
1946-
<https://peps.python.org/pep-0585>`__. On python>=3.10 these are fully
1942+
``list[<type>]`` instead of ``typing.List[<type>]``, see PEP `604
1943+
<https://peps.python.org/pep-0604>`__. On python>=3.10 these are fully
19471944
supported. On python<=3.9 backporting these types is attempted and in some cases
19481945
it can fail. On failure the type annotation is set to ``Any``.
19491946

README.rst

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -107,9 +107,8 @@ Other notable features include:
107107
- **Extensive type hint support:** nested types (union, optional), containers
108108
(list, dict, etc.), user-defined generics, restricted types (regex, numbers),
109109
paths, URLs, types from stubs (``*.pyi``), future annotations (PEP `563
110-
<https://peps.python.org/pep-0563/>`__), and backports (PEPs `604
111-
<https://peps.python.org/pep-0604>`__/`585
112-
<https://peps.python.org/pep-0585>`__).
110+
<https://peps.python.org/pep-0563/>`__), and backports (PEP `604
111+
<https://peps.python.org/pep-0604>`__).
113112

114113
- **Keyword arguments introspection:** resolving of parameters used via
115114
``**kwargs``.

jsonargparse/_core.py

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -301,9 +301,11 @@ def parse_known_args(self, args=None, namespace=None):
301301
namespace = argcomplete_namespace(caller, self, namespace)
302302

303303
try:
304-
with patch_namespace(), parser_context(
305-
parent_parser=self, lenient_check=True
306-
), ActionTypeHint.subclass_arg_context(self):
304+
with (
305+
patch_namespace(),
306+
parser_context(parent_parser=self, lenient_check=True),
307+
ActionTypeHint.subclass_arg_context(self),
308+
):
307309
kwargs = {}
308310
if _parse_known_has_intermixed:
309311
kwargs["intermixed"] = False

jsonargparse/_postponed_annotations.py

Lines changed: 6 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
from copy import deepcopy
88
from dataclasses import is_dataclass
99
from importlib import import_module
10-
from typing import Any, Dict, ForwardRef, FrozenSet, List, Optional, Set, Tuple, Type, Union, get_type_hints
10+
from typing import Any, ForwardRef, List, Optional, Union, get_type_hints
1111

1212
from ._optionals import typing_extensions_import
1313
from ._typehints import mapping_origin_types, sequence_origin_types, tuple_set_origin_types
@@ -16,28 +16,9 @@
1616
var_map = namedtuple("var_map", "name value")
1717
none_map = var_map(name="NoneType", value=type(None))
1818
union_map = var_map(name="Union", value=Union)
19-
pep585_map = {
20-
"dict": var_map(name="Dict", value=Dict),
21-
"frozenset": var_map(name="FrozenSet", value=FrozenSet),
22-
"list": var_map(name="List", value=List),
23-
"set": var_map(name="Set", value=Set),
24-
"tuple": var_map(name="Tuple", value=Tuple),
25-
"type": var_map(name="Type", value=Type),
26-
}
2719

2820

2921
class BackportTypeHints(ast.NodeTransformer):
30-
def visit_Subscript(self, node: ast.Subscript) -> ast.Subscript:
31-
if isinstance(node.value, ast.Name) and node.value.id in pep585_map:
32-
value = self.new_name_load(pep585_map[node.value.id])
33-
else:
34-
value = node.value # type: ignore[assignment]
35-
return ast.Subscript(
36-
value=value,
37-
slice=self.visit(node.slice),
38-
ctx=ast.Load(),
39-
)
40-
4122
def visit_Constant(self, node: ast.Constant) -> Union[ast.Constant, ast.Name]:
4223
if node.value is None:
4324
return self.new_name_load(none_map)
@@ -193,44 +174,24 @@ def get_arg_type(arg_ast, aliases):
193174
return exec_vars["___arg_type___"]
194175

195176

196-
def getattr_recursive(obj, attr):
197-
if "." in attr:
198-
attr, *attrs = attr.split(".", 1)
199-
return getattr_recursive(getattr(obj, attr), attrs[0])
200-
return getattr(obj, attr)
201-
202-
203177
def resolve_forward_refs(arg_type, aliases, logger):
204-
if isinstance(arg_type, str) and arg_type in aliases:
205-
arg_type = aliases[arg_type]
206178

207179
def resolve_subtypes_forward_refs(typehint):
208180
if has_subtypes(typehint):
209181
try:
210182
subtypes = []
211183
for arg in typehint.__args__:
212184
if isinstance(arg, ForwardRef):
213-
forward_arg, *forward_args = arg.__forward_arg__.split(".", 1)
185+
forward_arg, *_ = arg.__forward_arg__.split(".", 1)
214186
if forward_arg in aliases:
215187
arg = aliases[forward_arg]
216-
if forward_args:
217-
arg = getattr_recursive(arg, forward_args[0])
218188
else:
219189
raise NameError(f"Name '{forward_arg}' is not defined")
220190
else:
221191
arg = resolve_subtypes_forward_refs(arg)
222192
subtypes.append(arg)
223193
if subtypes != list(typehint.__args__):
224194
typehint_origin = get_typehint_origin(typehint)
225-
if sys.version_info < (3, 10):
226-
if typehint_origin in sequence_origin_types:
227-
typehint_origin = List
228-
elif typehint_origin in tuple_set_origin_types:
229-
typehint_origin = Tuple
230-
elif typehint_origin in mapping_origin_types:
231-
typehint_origin = Dict
232-
elif typehint_origin == type:
233-
typehint_origin = Type
234195
typehint = typehint_origin[tuple(subtypes)]
235196
except Exception as ex:
236197
if logger:
@@ -292,7 +253,7 @@ def get_types(obj: Any, logger: Optional[logging.Logger] = None) -> dict:
292253
except Exception as ex2:
293254
if isinstance(types, Exception):
294255
if logger:
295-
logger.debug(f"Failed to parse to source code for {obj}", exc_info=ex2)
256+
logger.debug(f"Failed to parse the source code for {obj}", exc_info=ex2)
296257
raise type(types)(f"{repr(types)} + {repr(ex2)}") from ex2 # type: ignore[arg-type]
297258
return types
298259

@@ -303,19 +264,13 @@ def get_types(obj: Any, logger: Optional[logging.Logger] = None) -> dict:
303264
ex = types
304265
types = {}
305266

306-
if isinstance(node, ast.FunctionDef):
307-
arg_asts = [(a.arg, a.annotation) for a in node.args.args + node.args.kwonlyargs]
308-
else:
309-
arg_asts = [(a.target.id, a.annotation) for a in node.body if isinstance(a, ast.AnnAssign)] # type: ignore[union-attr]
267+
arg_asts = [(a.arg, a.annotation) for a in node.args.args + node.args.kwonlyargs] # type: ignore[union-attr]
310268

311269
for name, annotation in arg_asts:
312270
if annotation and (name not in types or type_requires_eval(types[name])):
313271
try:
314-
if isinstance(annotation, ast.Constant) and annotation.value in aliases:
315-
types[name] = aliases[annotation.value]
316-
else:
317-
arg_type = get_arg_type(annotation, aliases)
318-
types[name] = resolve_forward_refs(arg_type, aliases, logger)
272+
arg_type = get_arg_type(annotation, aliases)
273+
types[name] = resolve_forward_refs(arg_type, aliases, logger)
319274
except Exception as ex3:
320275
types[name] = ex3
321276

@@ -355,8 +310,6 @@ def get_return_type(component, logger=None):
355310
global_vars = get_global_vars(component, logger)
356311
try:
357312
return_type = get_type_hints(component, global_vars)["return"]
358-
if isinstance(return_type, ForwardRef):
359-
return_type = resolve_forward_refs(return_type.__forward_arg__, global_vars, logger)
360313
except Exception as ex:
361314
if logger:
362315
logger.debug(f"Unable to evaluate types for {component}", exc_info=ex)

jsonargparse/_typehints.py

Lines changed: 0 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -947,17 +947,6 @@ def adapt_typehints(
947947
required_keys.difference_update(
948948
{k for k, v in typehint.__annotations__.items() if get_typehint_origin(v) in not_required_types}
949949
)
950-
# The standard library TypedDict in Python 3.8 does not store runtime information
951-
# about which (if any) keys are optional. See https://bugs.python.org/issue38834.
952-
# Thus, fall back to totality and explicitly Required keys
953-
elif typehint.__total__:
954-
required_keys = {
955-
k for k, v in typehint.__annotations__.items() if get_typehint_origin(v) not in not_required_types
956-
}
957-
else:
958-
required_keys = {
959-
k for k, v in typehint.__annotations__.items() if get_typehint_origin(v) in required_types
960-
}
961950
missing_keys = required_keys - val.keys()
962951
if missing_keys:
963952
raise_unexpected_value(f"Missing required keys: {missing_keys}", val)
@@ -1118,8 +1107,6 @@ def adapt_typehints(
11181107
return adapt_typehints(val, get_alias_target(typehint), **adapt_kwargs)
11191108

11201109
else:
1121-
if str(typehint) == "+VT_co":
1122-
return val # required for typing.Mapping in python 3.8
11231110
raise RuntimeError(f"The code should never reach here: typehint={typehint}") # pragma: no cover
11241111

11251112
return val

jsonargparse/_util.py

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -390,8 +390,6 @@ def class_from_function(
390390
if isinstance(func_return, str):
391391
try:
392392
func_return = get_type_hints(func)["return"]
393-
if isinstance(func_return, __import__("typing").ForwardRef):
394-
func_return = func_return._evaluate(func.__globals__, {})
395393
except Exception as ex:
396394
func_return = inspect.signature(func).return_annotation
397395
raise ValueError(f"Unable to dereference {func_return}, the return type of {func}: {ex}") from ex

jsonargparse_tests/conftest.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
import os
33
import platform
44
import re
5+
import sys
56
from contextlib import ExitStack, contextmanager, redirect_stderr, redirect_stdout
67
from functools import wraps
78
from importlib.util import find_spec
@@ -184,7 +185,9 @@ def capture_logs(logger: logging.Logger) -> Iterator[StringIO]:
184185

185186

186187
@contextmanager
187-
def source_unavailable():
188+
def source_unavailable(obj=None):
189+
if obj and obj.__module__ in sys.modules:
190+
del sys.modules[obj.__module__]
188191
with patch("inspect.getsource", side_effect=OSError("mock source code not available")):
189192
yield
190193

0 commit comments

Comments
 (0)