mirror of
https://github.com/ansible/ansible.git
synced 2025-11-30 23:16:08 +07:00
Drop Python 3.11 controller support (#85590)
This commit is contained in:
@@ -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)
|
||||||
|
|||||||
Reference in New Issue
Block a user