Drop Python 2.7 and Python 3.6 support (#81866)

* Drop Python 2.7 and Python 3.6 support

* Remove obsolete _json_compat
pull/81882/head
Matt Clay 8 months ago committed by GitHub
parent c1343cc304
commit b94ee1cefd
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -54,8 +54,6 @@ stages:
nameFormat: Python {0}
testFormat: units/{0}
targets:
- test: 2.7
- test: 3.6
- test: 3.7
- test: 3.8
- test: 3.9
@ -81,12 +79,6 @@ stages:
targets:
- name: macOS 13.2
test: macos/13.2
- name: RHEL 7.9
test: rhel/7.9
- name: RHEL 8.8 py36
test: rhel/8.8@3.6
- name: RHEL 8.8 py311
test: rhel/8.8@3.11
- name: RHEL 9.2 py39
test: rhel/9.2@3.9
- name: RHEL 9.2 py311
@ -101,8 +93,6 @@ stages:
targets:
- name: macOS 13.2
test: macos/13.2
- name: RHEL 8.8
test: rhel/8.8
- name: RHEL 9.2
test: rhel/9.2
- name: FreeBSD 13.2
@ -118,8 +108,6 @@ stages:
test: alpine/3.18
- name: Fedora 38
test: fedora/38
- name: RHEL 8.8
test: rhel/8.8
- name: RHEL 9.2
test: rhel/9.2
- name: Ubuntu 22.04
@ -135,12 +123,8 @@ stages:
targets:
- name: Alpine 3
test: alpine3
- name: CentOS 7
test: centos7
- name: Fedora 38
test: fedora38
- name: openSUSE 15
test: opensuse15
- name: Ubuntu 20.04
test: ubuntu2004
- name: Ubuntu 22.04

@ -1563,10 +1563,8 @@ INTERPRETER_PYTHON_FALLBACK:
- python3.9
- python3.8
- python3.7
- python3.6
- /usr/bin/python3
- /usr/libexec/platform-python
- python2.7
- /usr/bin/python
- python
vars:

@ -2,22 +2,20 @@
# Copyright (c), Toshio Kuratomi <tkuratomi@ansible.com> 2016
# Simplified BSD License (see licenses/simplified_bsd.txt or https://opensource.org/licenses/BSD-2-Clause)
from __future__ import absolute_import, division, print_function
__metaclass__ = type
from __future__ import annotations
import json
import sys
# Used for determining if the system is running a new enough python version
# and should only restrict on our documented minimum versions
_PY3_MIN = sys.version_info >= (3, 6)
_PY2_MIN = (2, 7) <= sys.version_info < (3,)
_PY_MIN = _PY3_MIN or _PY2_MIN
if not _PY_MIN:
print(
'\n{"failed": true, '
'"msg": "ansible-core requires a minimum of Python2 version 2.7 or Python3 version 3.6. Current version: %s"}' % ''.join(sys.version.splitlines())
)
_PY_MIN = (3, 7)
if sys.version_info < _PY_MIN:
print(json.dumps(dict(
failed=True,
msg=f"ansible-core requires a minimum of Python version {'.'.join(map(str, _PY_MIN))}. Current version: {''.join(sys.version.splitlines())}",
)))
sys.exit(1)
# Ansible modules can be written in any language.
@ -128,12 +126,6 @@ def _get_available_hash_algorithms():
AVAILABLE_HASH_ALGORITHMS = _get_available_hash_algorithms()
try:
from ansible.module_utils.common._json_compat import json
except ImportError as e:
print('\n{{"msg": "Error: ansible requires the stdlib json: {0}", "failed": true}}'.format(to_native(e)))
sys.exit(1)
from ansible.module_utils.six.moves.collections_abc import (
KeysView,
Mapping, MutableMapping,

@ -1,16 +0,0 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2019 Ansible Project
# Simplified BSD License (see licenses/simplified_bsd.txt or https://opensource.org/licenses/BSD-2-Clause)
from __future__ import absolute_import, division, print_function
__metaclass__ = type
import types
import json
# Detect the python-json library which is incompatible
try:
if not isinstance(json.loads, types.FunctionType) or not isinstance(json.dumps, types.FunctionType):
raise ImportError('json.loads or json.dumps were not found in the imported json library.')
except AttributeError:
raise ImportError('python-json was detected, which is incompatible.')

@ -5,12 +5,12 @@
from __future__ import absolute_import, division, print_function
__metaclass__ = type
import json
import os
import re
from ast import literal_eval
from ansible.module_utils.common.text.converters import to_native
from ansible.module_utils.common._json_compat import json
from ansible.module_utils.common.collections import is_iterable
from ansible.module_utils.common.text.converters import jsonify
from ansible.module_utils.common.text.formatters import human_to_bytes

@ -1,9 +1,7 @@
base image=quay.io/ansible/base-test-container:5.9.0 python=3.11,2.7,3.6,3.7,3.8,3.9,3.10,3.12
default image=quay.io/ansible/default-test-container:9.2.0 python=3.11,2.7,3.6,3.7,3.8,3.9,3.10,3.12 context=collection
default image=quay.io/ansible/ansible-core-test-container:9.2.0 python=3.11,2.7,3.6,3.7,3.8,3.9,3.10,3.12 context=ansible-core
base image=quay.io/ansible/base-test-container:5.9.0 python=3.11,3.7,3.8,3.9,3.10,3.12
default image=quay.io/ansible/default-test-container:9.2.0 python=3.11,3.7,3.8,3.9,3.10,3.12 context=collection
default image=quay.io/ansible/ansible-core-test-container:9.2.0 python=3.11,3.7,3.8,3.9,3.10,3.12 context=ansible-core
alpine3 image=quay.io/ansible/alpine3-test-container:6.3.0 python=3.11 cgroup=none audit=none
centos7 image=quay.io/ansible/centos7-test-container:6.3.0 python=2.7 cgroup=v1-only
fedora38 image=quay.io/ansible/fedora38-test-container:6.3.0 python=3.11
opensuse15 image=quay.io/ansible/opensuse15-test-container:6.3.0 python=3.6
ubuntu2004 image=quay.io/ansible/ubuntu2004-test-container:6.3.0 python=3.8
ubuntu2204 image=quay.io/ansible/ubuntu2204-test-container:6.3.0 python=3.10

@ -6,8 +6,6 @@ freebsd/13.2 python=3.9,3.11 python_dir=/usr/local/bin become=su_sudo provider=a
freebsd python_dir=/usr/local/bin become=su_sudo provider=aws arch=x86_64
macos/13.2 python=3.11 python_dir=/usr/local/bin become=sudo provider=parallels arch=x86_64
macos python_dir=/usr/local/bin become=sudo provider=parallels arch=x86_64
rhel/7.9 python=2.7 become=sudo provider=aws arch=x86_64
rhel/8.8 python=3.6,3.11 become=sudo provider=aws arch=x86_64
rhel/9.2 python=3.9,3.11 become=sudo provider=aws arch=x86_64
rhel become=sudo provider=aws arch=x86_64
ubuntu/22.04 python=3.10 become=sudo provider=aws arch=x86_64

@ -764,11 +764,6 @@ class SanityTest(metaclass=abc.ABCMeta):
"""True if the test targets should include symlinks."""
return False
@property
def py2_compat(self) -> bool:
"""True if the test only applies to code that runs on Python 2.x."""
return False
@property
def supported_python_versions(self) -> t.Optional[tuple[str, ...]]:
"""A tuple of supported Python versions or None if the test does not depend on specific Python versions."""
@ -786,23 +781,11 @@ class SanityTest(metaclass=abc.ABCMeta):
def filter_targets_by_version(self, args: SanityConfig, targets: list[TestTarget], python_version: str) -> list[TestTarget]:
"""Return the given list of test targets, filtered to include only those relevant for the test, taking into account the Python version."""
del args # args is not used here, but derived classes may make use of it
del python_version # python_version is not used here, but derived classes may make use of it
targets = self.filter_targets(targets)
if self.py2_compat:
# This sanity test is a Python 2.x compatibility test.
content_config = get_content_config(args)
if content_config.py2_support:
# This collection supports Python 2.x.
# Filter targets to include only those that require support for remote-only Python versions.
targets = self.filter_remote_targets(targets)
else:
# This collection does not support Python 2.x.
# There are no targets to test.
targets = []
return targets
@staticmethod
@ -878,7 +861,6 @@ class SanityCodeSmellTest(SanitySingleVersion):
self.__no_targets: bool = self.config.get('no_targets')
self.__include_directories: bool = self.config.get('include_directories')
self.__include_symlinks: bool = self.config.get('include_symlinks')
self.__py2_compat: bool = self.config.get('py2_compat', False)
self.__error_code: str | None = self.config.get('error_code', None)
else:
self.output = None
@ -894,7 +876,6 @@ class SanityCodeSmellTest(SanitySingleVersion):
self.__no_targets = True
self.__include_directories = False
self.__include_symlinks = False
self.__py2_compat = False
self.__error_code = None
if self.no_targets:
@ -939,11 +920,6 @@ class SanityCodeSmellTest(SanitySingleVersion):
"""True if the test targets should include symlinks."""
return self.__include_symlinks
@property
def py2_compat(self) -> bool:
"""True if the test only applies to code that runs on Python 2.x."""
return self.__py2_compat
@property
def supported_python_versions(self) -> t.Optional[tuple[str, ...]]:
"""A tuple of supported Python versions or None if the test does not depend on specific Python versions."""

@ -446,17 +446,7 @@ def get_venv_packages(python: PythonConfig) -> dict[str, str]:
wheel='0.37.1',
)
override_packages = {
'2.7': dict(
pip='20.3.4', # 21.0 requires Python 3.6+
setuptools='44.1.1', # 45.0.0 requires Python 3.5+
wheel=None,
),
'3.6': dict(
pip='21.3.1', # 22.0 requires Python 3.7+
setuptools='59.6.0', # 59.7.0 requires Python 3.7+
wheel=None,
),
override_packages: dict[str, dict[str, str]] = {
}
packages = {name: version or default_packages[name] for name, version in override_packages.get(python.version, default_packages).items()}

@ -1,7 +0,0 @@
{
"extensions": [
".py"
],
"py2_compat": true,
"output": "path-message"
}

@ -1,46 +0,0 @@
"""Enforce proper usage of __future__ imports."""
from __future__ import annotations
import ast
import sys
def main():
"""Main entry point."""
for path in sys.argv[1:] or sys.stdin.read().splitlines():
with open(path, 'rb') as path_fd:
lines = path_fd.read().splitlines()
missing = True
if not lines:
# Files are allowed to be empty of everything including boilerplate
missing = False
for text in lines:
if text in (b'from __future__ import (absolute_import, division, print_function)',
b'from __future__ import absolute_import, division, print_function'):
missing = False
break
if missing:
with open(path, encoding='utf-8') as file:
contents = file.read()
# noinspection PyBroadException
try:
node = ast.parse(contents)
# files consisting of only assignments have no need for future import boilerplate
# the only exception would be division during assignment, but we'll overlook that for simplicity
# the most likely case is that of a documentation only python file
if all(isinstance(statement, ast.Assign) for statement in node.body):
missing = False
except Exception: # pylint: disable=broad-except
pass # the compile sanity test will report this error
if missing:
print('%s: missing: from __future__ import (absolute_import, division, print_function)' % path)
if __name__ == '__main__':
main()

@ -1,7 +0,0 @@
{
"extensions": [
".py"
],
"py2_compat": true,
"output": "path-message"
}

@ -1,44 +0,0 @@
"""Require __metaclass__ boilerplate for code that supports Python 2.x."""
from __future__ import annotations
import ast
import sys
def main():
"""Main entry point."""
for path in sys.argv[1:] or sys.stdin.read().splitlines():
with open(path, 'rb') as path_fd:
lines = path_fd.read().splitlines()
missing = True
if not lines:
# Files are allowed to be empty of everything including boilerplate
missing = False
for text in lines:
if text == b'__metaclass__ = type':
missing = False
break
if missing:
with open(path, encoding='utf-8') as file:
contents = file.read()
# noinspection PyBroadException
try:
node = ast.parse(contents)
# files consisting of only assignments have no need for metaclass boilerplate
# the most likely case is that of a documentation only python file
if all(isinstance(statement, ast.Assign) for statement in node.body):
missing = False
except Exception: # pylint: disable=broad-except
pass # the compile sanity test will report this error
if missing:
print('%s: missing: __metaclass__ = type' % path)
if __name__ == '__main__':
main()

@ -4,8 +4,7 @@
# NOTE: This file resides in the _util/target directory to ensure compatibility with all supported Python versions.
from __future__ import (absolute_import, division, print_function)
__metaclass__ = type
from __future__ import annotations
import os
import sys

@ -2,12 +2,9 @@
# NOTE: This file resides in the _util/target directory to ensure compatibility with all supported Python versions.
from __future__ import (absolute_import, division, print_function)
__metaclass__ = type
from __future__ import annotations
REMOTE_ONLY_PYTHON_VERSIONS = (
'2.7',
'3.6',
'3.7',
'3.8',
'3.9',

@ -1,7 +1,6 @@
# auto-shebang
"""Provides an entry point for python scripts and python modules on the controller with the current python interpreter and optional code coverage collection."""
from __future__ import (absolute_import, division, print_function)
__metaclass__ = type
from __future__ import annotations
import os
import sys

@ -4,9 +4,7 @@
# https://github.com/pytest-dev/pytest-forked
# https://github.com/pytest-dev/py
# TIP: Disable pytest-xdist when debugging internal errors in this plugin.
from __future__ import absolute_import, division, print_function
__metaclass__ = type
from __future__ import annotations
import os
import pickle

@ -1,6 +1,5 @@
"""Enable unit testing of Ansible collections. PYTEST_DONT_REWRITE"""
from __future__ import (absolute_import, division, print_function)
__metaclass__ = type
from __future__ import annotations
import os

@ -1,6 +1,5 @@
"""Monkey patch os._exit when running under coverage so we don't lose coverage data in forks, such as with `pytest --boxed`. PYTEST_DONT_REWRITE"""
from __future__ import (absolute_import, division, print_function)
__metaclass__ = type
from __future__ import annotations
def pytest_configure():

@ -1,6 +1,5 @@
"""Python syntax checker with lint friendly output."""
from __future__ import (absolute_import, division, print_function)
__metaclass__ = type
from __future__ import annotations
import sys

@ -1,6 +1,5 @@
"""Import the given python module(s) and report error(s) encountered."""
from __future__ import (absolute_import, division, print_function)
__metaclass__ = type
from __future__ import annotations
def main():

@ -233,79 +233,6 @@ bootstrap_remote_macos()
echo 'PATH="/usr/local/bin:$PATH"' > /etc/zshenv
}
bootstrap_remote_rhel_7()
{
packages="
gcc
python-devel
python-virtualenv
"
while true; do
# shellcheck disable=SC2086
yum install -q -y ${packages} \
&& break
echo "Failed to install packages. Sleeping before trying again..."
sleep 10
done
install_pip
bootstrap_remote_rhel_pinned_pip_packages
}
bootstrap_remote_rhel_8()
{
if [ "${python_version}" = "3.6" ]; then
py_pkg_prefix="python3"
else
py_pkg_prefix="python${python_version}"
fi
packages="
gcc
${py_pkg_prefix}-devel
"
# pip isn't included in the Python devel package under Python 3.11
if [ "${python_version}" != "3.6" ]; then
packages="
${packages}
${py_pkg_prefix}-pip
"
fi
# Jinja2 is not installed with an OS package since the provided version is too old.
# Instead, ansible-test will install it using pip.
if [ "${controller}" ]; then
packages="
${packages}
${py_pkg_prefix}-cryptography
"
fi
# Python 3.11 isn't a module like the earlier versions
if [ "${python_version}" = "3.6" ]; then
while true; do
# shellcheck disable=SC2086
yum module install -q -y "python${python_package_version}" \
&& break
echo "Failed to install packages. Sleeping before trying again..."
sleep 10
done
fi
while true; do
# shellcheck disable=SC2086
yum install -q -y ${packages} \
&& break
echo "Failed to install packages. Sleeping before trying again..."
sleep 10
done
bootstrap_remote_rhel_pinned_pip_packages
}
bootstrap_remote_rhel_9()
{
if [ "${python_version}" = "3.9" ]; then
@ -351,8 +278,6 @@ bootstrap_remote_rhel_9()
bootstrap_remote_rhel()
{
case "${platform_version}" in
7.*) bootstrap_remote_rhel_7 ;;
8.*) bootstrap_remote_rhel_8 ;;
9.*) bootstrap_remote_rhel_9 ;;
esac
}

@ -1,6 +1,5 @@
"""A tool for probing cgroups to determine write access."""
from __future__ import (absolute_import, division, print_function)
__metaclass__ = type
from __future__ import annotations
import json
import os

@ -1,6 +1,5 @@
"""Custom entry-point for pip that filters out unwanted logging and warnings."""
from __future__ import (absolute_import, division, print_function)
__metaclass__ = type
from __future__ import annotations
import logging
import os

@ -1,6 +1,5 @@
"""A tool for installing test requirements on the controller and target host."""
from __future__ import (absolute_import, division, print_function)
__metaclass__ = type
from __future__ import annotations
# pylint: disable=wrong-import-position

@ -1,6 +1,5 @@
"""Detect the real python interpreter when running in a virtual environment created by the 'virtualenv' module."""
from __future__ import (absolute_import, division, print_function)
__metaclass__ = type
from __future__ import annotations
import json

@ -1,6 +1,5 @@
"""Show availability of PyYAML and libyaml support."""
from __future__ import (absolute_import, division, print_function)
__metaclass__ = type
from __future__ import annotations
import json

@ -5,15 +5,7 @@ import sys
def main():
# The following directories contain code which must work under Python 2.x.
py2_compat = (
'test/lib/ansible_test/_util/target/',
)
for path in sys.argv[1:] or sys.stdin.read().splitlines():
if any(path.startswith(prefix) for prefix in py2_compat):
continue
with open(path, 'rb') as path_fd:
lines = path_fd.read().splitlines()

@ -50,18 +50,12 @@ lib/ansible/modules/user.py validate-modules:doc-default-does-not-match-spec
lib/ansible/modules/user.py validate-modules:use-run-command-not-popen
lib/ansible/modules/yum.py validate-modules:parameter-invalid
lib/ansible/module_utils/basic.py pylint:unused-import # deferring resolution to allow enabling the rule now
lib/ansible/module_utils/compat/_selectors2.py future-import-boilerplate # ignore bundled
lib/ansible/module_utils/compat/_selectors2.py metaclass-boilerplate # ignore bundled
lib/ansible/module_utils/compat/selinux.py import-2.7!skip # pass/fail depends on presence of libselinux.so
lib/ansible/module_utils/compat/selinux.py import-3.6!skip # pass/fail depends on presence of libselinux.so
lib/ansible/module_utils/compat/selinux.py import-3.7!skip # pass/fail depends on presence of libselinux.so
lib/ansible/module_utils/compat/selinux.py import-3.8!skip # pass/fail depends on presence of libselinux.so
lib/ansible/module_utils/compat/selinux.py import-3.9!skip # pass/fail depends on presence of libselinux.so
lib/ansible/module_utils/compat/selinux.py import-3.10!skip # pass/fail depends on presence of libselinux.so
lib/ansible/module_utils/compat/selinux.py import-3.11!skip # pass/fail depends on presence of libselinux.so
lib/ansible/module_utils/compat/selinux.py import-3.12!skip # pass/fail depends on presence of libselinux.so
lib/ansible/module_utils/distro/_distro.py future-import-boilerplate # ignore bundled
lib/ansible/module_utils/distro/_distro.py metaclass-boilerplate # ignore bundled
lib/ansible/module_utils/distro/_distro.py no-assert
lib/ansible/module_utils/distro/_distro.py pep8!skip # bundled code we don't want to modify
lib/ansible/module_utils/distro/_distro.py pylint:undefined-variable # ignore bundled
@ -78,8 +72,6 @@ lib/ansible/module_utils/powershell/Ansible.ModuleUtils.Legacy.psm1 pslint:PSUse
lib/ansible/module_utils/powershell/Ansible.ModuleUtils.LinkUtil.psm1 pslint:PSUseApprovedVerbs
lib/ansible/module_utils/pycompat24.py no-get-exception
lib/ansible/module_utils/six/__init__.py empty-init # breaks namespacing, bundled, do not override
lib/ansible/module_utils/six/__init__.py future-import-boilerplate # ignore bundled
lib/ansible/module_utils/six/__init__.py metaclass-boilerplate # ignore bundled
lib/ansible/module_utils/six/__init__.py no-basestring
lib/ansible/module_utils/six/__init__.py no-dict-iteritems
lib/ansible/module_utils/six/__init__.py no-dict-iterkeys
@ -126,7 +118,6 @@ test/integration/targets/module_precedence/lib_with_extension/a.ini shebang
test/integration/targets/module_precedence/lib_with_extension/ping.ini shebang
test/integration/targets/module_precedence/roles_with_extension/foo/library/a.ini shebang
test/integration/targets/module_precedence/roles_with_extension/foo/library/ping.ini shebang
test/integration/targets/module_utils/library/test.py future-import-boilerplate # allow testing of Python 2.x implicit relative imports
test/integration/targets/old_style_modules_posix/library/helloworld.sh shebang
test/integration/targets/template/files/encoding_1252_utf-8.expected no-smart-quotes
test/integration/targets/template/files/encoding_1252_windows-1252.expected no-smart-quotes

@ -42,7 +42,6 @@ MODULE_UTILS_BASIC_FILES = frozenset(('ansible/__init__.py',
'ansible/module_utils/basic.py',
'ansible/module_utils/six/__init__.py',
'ansible/module_utils/_text.py',
'ansible/module_utils/common/_json_compat.py',
'ansible/module_utils/common/collections.py',
'ansible/module_utils/common/parameters.py',
'ansible/module_utils/common/warnings.py',

@ -61,20 +61,6 @@ class TestImports(ModuleTestCase):
mod = builtins.__import__('ansible.module_utils.basic')
self.assertFalse(mod.module_utils.basic.HAVE_SELINUX)
@patch.object(builtins, '__import__')
def test_module_utils_basic_import_json(self, mock_import):
def _mock_import(name, *args, **kwargs):
if name == 'ansible.module_utils.common._json_compat':
raise ImportError
return realimport(name, *args, **kwargs)
self.clear_modules(['json', 'ansible.module_utils.basic'])
builtins.__import__('ansible.module_utils.basic')
self.clear_modules(['json', 'ansible.module_utils.basic'])
mock_import.side_effect = _mock_import
with self.assertRaises(SystemExit):
builtins.__import__('ansible.module_utils.basic')
# FIXME: doesn't work yet
# @patch.object(builtins, 'bytes')
# def test_module_utils_basic_bytes(self, mock_bytes):

Loading…
Cancel
Save