Drop Python 3.11 controller support (#85590)

pull/85602/head
Matt Clay 4 months ago committed by GitHub
parent 8aad1418f6
commit f2612fbe3a
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -112,10 +112,6 @@ stages:
test: rhel/9.6 test: rhel/9.6
- name: RHEL 10.0 - name: RHEL 10.0
test: rhel/10.0 test: rhel/10.0
- name: FreeBSD 13.5
test: freebsd/13.5
- name: FreeBSD 14.3
test: freebsd/14.3
groups: groups:
- 3 - 3
- 4 - 4
@ -183,9 +179,9 @@ stages:
nameFormat: Python {0} nameFormat: Python {0}
testFormat: galaxy/{0}/1 testFormat: galaxy/{0}/1
targets: targets:
- test: 3.11
- test: 3.12 - test: 3.12
- test: 3.13 - test: 3.13
- test: 3.14
- stage: Generic - stage: Generic
dependsOn: [] dependsOn: []
jobs: jobs:
@ -194,9 +190,9 @@ stages:
nameFormat: Python {0} nameFormat: Python {0}
testFormat: generic/{0}/1 testFormat: generic/{0}/1
targets: targets:
- test: 3.11
- test: 3.12 - test: 3.12
- test: 3.13 - test: 3.13
- test: 3.14
- stage: Incidental_Windows - stage: Incidental_Windows
displayName: Incidental Windows displayName: Incidental Windows
dependsOn: [] dependsOn: []

@ -1,3 +1,4 @@
major_changes: major_changes:
- ansible - Add support for Python 3.14. - ansible - Add support for Python 3.14.
- ansible - Drop support for Python 3.8 on targets. - ansible - Drop support for Python 3.8 on targets.
- ansible - Drop support for Python 3.11 on the controller.

@ -5,7 +5,7 @@ env-setup
--------- ---------
The 'env-setup' script modifies your environment to allow you to run The 'env-setup' script modifies your environment to allow you to run
ansible from a git checkout using python >= 3.11. ansible from a git checkout using a supported Python version.
First, set up your environment to run from the checkout: First, set up your environment to run from the checkout:

@ -30,10 +30,7 @@ def import_controller_module(module_name: str, /) -> t.Any:
return importlib.import_module(module_name) return importlib.import_module(module_name)
_T = t.TypeVar('_T') def experimental[T](obj: T) -> T:
def experimental(obj: _T) -> _T:
""" """
Decorator for experimental types and methods outside the `_internal` package which accept or expose internal types. Decorator for experimental types and methods outside the `_internal` package which accept or expose internal types.
As with internal APIs, these are subject to change at any time without notice. As with internal APIs, these are subject to change at any time without notice.

@ -9,8 +9,6 @@ from ansible.module_utils._internal._ansiballz import _extensions
from ansible.module_utils._internal._ansiballz._extensions import _debugpy, _pydevd, _coverage from ansible.module_utils._internal._ansiballz._extensions import _debugpy, _pydevd, _coverage
from ansible.constants import config from ansible.constants import config
_T = t.TypeVar('_T')
class ExtensionManager: class ExtensionManager:
"""AnsiballZ extension manager.""" """AnsiballZ extension manager."""
@ -101,7 +99,7 @@ class ExtensionManager:
) )
@classmethod @classmethod
def _get_options(cls, name: str, config_type: type[_T], task_vars: dict[str, object]) -> _T | None: def _get_options[T](cls, name: str, config_type: type[T], task_vars: dict[str, object]) -> T | None:
"""Parse configuration from the named environment variable as the specified type, or None if not configured.""" """Parse configuration from the named environment variable as the specified type, or None if not configured."""
if (value := config.get_config_value(name, variables=task_vars)) is None: if (value := config.get_config_value(name, variables=task_vars)) is None:
return None return None

@ -3,26 +3,24 @@ from __future__ import annotations as _annotations
import collections.abc as _c import collections.abc as _c
import typing as _t import typing as _t
_T_co = _t.TypeVar('_T_co', covariant=True)
class SequenceProxy[T](_c.Sequence[T]):
class SequenceProxy(_c.Sequence[_T_co]):
"""A read-only sequence proxy.""" """A read-only sequence proxy."""
# DTFIX5: needs unit test coverage # DTFIX5: needs unit test coverage
__slots__ = ('__value',) __slots__ = ('__value',)
def __init__(self, value: _c.Sequence[_T_co]) -> None: def __init__(self, value: _c.Sequence[T]) -> None:
self.__value = value self.__value = value
@_t.overload @_t.overload
def __getitem__(self, index: int) -> _T_co: ... def __getitem__(self, index: int) -> T: ...
@_t.overload @_t.overload
def __getitem__(self, index: slice) -> _c.Sequence[_T_co]: ... def __getitem__(self, index: slice) -> _c.Sequence[T]: ...
def __getitem__(self, index: int | slice) -> _T_co | _c.Sequence[_T_co]: def __getitem__(self, index: int | slice) -> T | _c.Sequence[T]:
if isinstance(index, slice): if isinstance(index, slice):
return self.__class__(self.__value[index]) return self.__class__(self.__value[index])
@ -34,10 +32,10 @@ class SequenceProxy(_c.Sequence[_T_co]):
def __contains__(self, item: object) -> bool: def __contains__(self, item: object) -> bool:
return item in self.__value return item in self.__value
def __iter__(self) -> _t.Iterator[_T_co]: def __iter__(self) -> _t.Iterator[T]:
yield from self.__value yield from self.__value
def __reversed__(self) -> _c.Iterator[_T_co]: def __reversed__(self) -> _c.Iterator[T]:
return reversed(self.__value) return reversed(self.__value)
def index(self, *args) -> int: def index(self, *args) -> int:

@ -24,7 +24,6 @@ from ansible._internal._templating import _transform
from ansible.module_utils import _internal from ansible.module_utils import _internal
from ansible.module_utils._internal import _datatag from ansible.module_utils._internal import _datatag
_T = t.TypeVar('_T')
_sentinel = object() _sentinel = object()
@ -115,7 +114,7 @@ class AnsibleVariableVisitor:
if func := getattr(super(), '__exit__', None): if func := getattr(super(), '__exit__', None):
func(*args, **kwargs) func(*args, **kwargs)
def visit(self, value: _T) -> _T: def visit[T](self, value: T) -> T:
""" """
Enforces Ansible's variable type system restrictions before a var is accepted in inventory. Also, conditionally implements template trust Enforces Ansible's variable type system restrictions before a var is accepted in inventory. Also, conditionally implements template trust
compatibility, depending on the plugin's declared understanding (or lack thereof). This always recursively copies inputs to fully isolate compatibility, depending on the plugin's declared understanding (or lack thereof). This always recursively copies inputs to fully isolate
@ -143,7 +142,7 @@ class AnsibleVariableVisitor:
return self._visit(None, key) # key=None prevents state tracking from seeing the key as value return self._visit(None, key) # key=None prevents state tracking from seeing the key as value
def _visit(self, key: t.Any, value: _T) -> _T: def _visit[T](self, key: t.Any, value: T) -> T:
"""Internal implementation to recursively visit a data structure's contents.""" """Internal implementation to recursively visit a data structure's contents."""
self._current = key # supports StateTrackingMixIn self._current = key # supports StateTrackingMixIn
@ -168,7 +167,7 @@ class AnsibleVariableVisitor:
value = value._native_copy() value = value._native_copy()
value_type = type(value) value_type = type(value)
result: _T result: T
# DTFIX-FUTURE: Visitor generally ignores dict/mapping keys by default except for debugging and schema-aware checking. # DTFIX-FUTURE: Visitor generally ignores dict/mapping keys by default except for debugging and schema-aware checking.
# It could be checking keys destined for variable storage to apply more strict rules about key shape and type. # It could be checking keys destined for variable storage to apply more strict rules about key shape and type.

@ -29,7 +29,6 @@ from ._utils import LazyOptions, TemplateContext
_display = Display() _display = Display()
_TCallable = t.TypeVar("_TCallable", bound=t.Callable)
_ITERATOR_TYPES: t.Final = (c.Iterator, c.ItemsView, c.KeysView, c.ValuesView, range) _ITERATOR_TYPES: t.Final = (c.Iterator, c.ItemsView, c.KeysView, c.ValuesView, range)
@ -169,7 +168,7 @@ class _DirectCall:
_marker_attr: t.Final[str] = "_directcall" _marker_attr: t.Final[str] = "_directcall"
@classmethod @classmethod
def mark(cls, src: _TCallable) -> _TCallable: def mark[T: t.Callable](cls, src: T) -> T:
setattr(src, cls._marker_attr, True) setattr(src, cls._marker_attr, True)
return src return src

@ -23,7 +23,7 @@ if 1 <= len(sys.argv) <= 2 and os.path.basename(sys.argv[0]) == "ansible" and os
# Used for determining if the system is running a new enough python version # Used for determining if the system is running a new enough python version
# and should only restrict on our documented minimum versions # and should only restrict on our documented minimum versions
_PY_MIN = (3, 11) _PY_MIN = (3, 12)
if sys.version_info < _PY_MIN: if sys.version_info < _PY_MIN:
raise SystemExit( raise SystemExit(

@ -1689,12 +1689,12 @@ INTERPRETER_PYTHON:
INTERPRETER_PYTHON_FALLBACK: INTERPRETER_PYTHON_FALLBACK:
name: Ordered list of Python interpreters to check for in discovery name: Ordered list of Python interpreters to check for in discovery
default: default:
- python3.14
- python3.13 - python3.13
- python3.12 - python3.12
- python3.11 - python3.11
- python3.10 - python3.10
- python3.9 - python3.9
- python3.8
- /usr/bin/python3 - /usr/bin/python3
- python3 - python3
vars: vars:

@ -1271,11 +1271,7 @@ def test_sdist() -> None:
except FileNotFoundError: except FileNotFoundError:
raise ApplicationError(f"Missing sdist: {sdist_file.relative_to(CHECKOUT_DIR)}") from None raise ApplicationError(f"Missing sdist: {sdist_file.relative_to(CHECKOUT_DIR)}") from None
# deprecated: description='extractall fallback without filter' python_version='3.11' sdist.extractall(temp_dir, filter='data')
if hasattr(tarfile, 'data_filter'):
sdist.extractall(temp_dir, filter='data') # type: ignore[call-arg]
else:
sdist.extractall(temp_dir)
pyc_glob = "*.pyc*" pyc_glob = "*.pyc*"
pyc_files = sorted(path.relative_to(temp_dir) for path in temp_dir.rglob(pyc_glob)) pyc_files = sorted(path.relative_to(temp_dir) for path in temp_dir.rglob(pyc_glob))

@ -3,7 +3,7 @@ requires = ["setuptools >= 66.1.0, <= 80.3.1", "wheel == 0.45.1"] # lower bound
build-backend = "setuptools.build_meta" build-backend = "setuptools.build_meta"
[project] [project]
requires-python = ">=3.11" requires-python = ">=3.12"
name = "ansible-core" name = "ansible-core"
authors = [ authors = [
{name = "Ansible Project"}, {name = "Ansible Project"},
@ -20,9 +20,9 @@ classifiers = [
"Natural Language :: English", "Natural Language :: English",
"Operating System :: POSIX", "Operating System :: POSIX",
"Programming Language :: Python :: 3", "Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12", "Programming Language :: Python :: 3.12",
"Programming Language :: Python :: 3.13", "Programming Language :: Python :: 3.13",
"Programming Language :: Python :: 3.14",
"Programming Language :: Python :: 3 :: Only", "Programming Language :: Python :: 3 :: Only",
"Topic :: System :: Installation/Setup", "Topic :: System :: Installation/Setup",
"Topic :: System :: Systems Administration", "Topic :: System :: Systems Administration",

@ -160,11 +160,7 @@ class ValidateModulesTest(SanitySingleVersion):
temp_dir = process_scoped_temporary_directory(args) temp_dir = process_scoped_temporary_directory(args)
with tarfile.open(path) as file: with tarfile.open(path) as file:
# deprecated: description='extractall fallback without filter' python_version='3.11' file.extractall(temp_dir, filter='data')
if hasattr(tarfile, 'data_filter'):
file.extractall(temp_dir, filter='data') # type: ignore[call-arg]
else:
file.extractall(temp_dir)
cmd.extend([ cmd.extend([
'--original-plugins', temp_dir, '--original-plugins', temp_dir,

@ -7,10 +7,10 @@ from __future__ import annotations
REMOTE_ONLY_PYTHON_VERSIONS = ( REMOTE_ONLY_PYTHON_VERSIONS = (
'3.9', '3.9',
'3.10', '3.10',
'3.11',
) )
CONTROLLER_PYTHON_VERSIONS = ( CONTROLLER_PYTHON_VERSIONS = (
'3.11',
'3.12', '3.12',
'3.13', '3.13',
'3.14', '3.14',

@ -187,12 +187,6 @@ bootstrap_remote_freebsd()
# Declare platform/python version combinations which do not have supporting OS packages available. # Declare platform/python version combinations which do not have supporting OS packages available.
# For these combinations ansible-test will use pip to install the requirements instead. # For these combinations ansible-test will use pip to install the requirements instead.
case "${platform_version}/${python_version}" in case "${platform_version}/${python_version}" in
13.5/3.11)
# defaults available
;;
14.3/3.11)
# defaults available
;;
*) *)
# just assume nothing is available # just assume nothing is available
jinja2_pkg="" # not available jinja2_pkg="" # not available

@ -54,7 +54,6 @@ lib/ansible/plugins/cache/base.py ansible-doc!skip # not a plugin, but a stub f
lib/ansible/plugins/callback/__init__.py pylint:arguments-renamed lib/ansible/plugins/callback/__init__.py pylint:arguments-renamed
lib/ansible/plugins/inventory/advanced_host_list.py pylint:arguments-renamed lib/ansible/plugins/inventory/advanced_host_list.py pylint:arguments-renamed
lib/ansible/plugins/inventory/host_list.py pylint:arguments-renamed lib/ansible/plugins/inventory/host_list.py pylint:arguments-renamed
lib/ansible/_internal/_wrapt.py mypy-3.11!skip # vendored code
lib/ansible/_internal/_wrapt.py mypy-3.12!skip # vendored code lib/ansible/_internal/_wrapt.py mypy-3.12!skip # vendored code
lib/ansible/_internal/_wrapt.py mypy-3.13!skip # vendored code lib/ansible/_internal/_wrapt.py mypy-3.13!skip # vendored code
lib/ansible/_internal/_wrapt.py mypy-3.14!skip # vendored code lib/ansible/_internal/_wrapt.py mypy-3.14!skip # vendored code
@ -237,3 +236,4 @@ lib/ansible/utils/encrypt.py pylint:ansible-deprecated-version # TODO: 2.20
lib/ansible/utils/ssh_functions.py pylint:ansible-deprecated-version # TODO: 2.20 lib/ansible/utils/ssh_functions.py pylint:ansible-deprecated-version # TODO: 2.20
lib/ansible/vars/manager.py pylint:ansible-deprecated-version-comment # TODO: 2.20 lib/ansible/vars/manager.py pylint:ansible-deprecated-version-comment # TODO: 2.20
lib/ansible/vars/plugins.py pylint:ansible-deprecated-version # TODO: 2.20 lib/ansible/vars/plugins.py pylint:ansible-deprecated-version # TODO: 2.20
lib/ansible/galaxy/role.py pylint:ansible-deprecated-python-version-comment # TODO: 2.20

@ -1,5 +1,5 @@
bcrypt ; python_version >= '3.11' # controller only bcrypt ; python_version >= '3.12' # controller only
passlib ; python_version >= '3.11' # controller only passlib ; python_version >= '3.12' # controller only
pexpect ; python_version >= '3.11' # controller only pexpect ; python_version >= '3.12' # controller only
pywinrm ; python_version >= '3.11' # controller only pywinrm ; python_version >= '3.12' # controller only
typing_extensions; python_version < '3.11' # some unit tests need Annotated and get_type_hints(include_extras=True) typing_extensions; python_version < '3.11' # some unit tests need Annotated and get_type_hints(include_extras=True)

Loading…
Cancel
Save