Drop Python 3.12 controller support (#87057)

* Drop Python 3.12 controller support

* Remove obsolete code

* Remove obsolete centos6 test code

* Skip tests on unsupported platforms

* Work around lack of Alpine controller support in tests
This commit is contained in:
Matt Clay
2026-06-05 10:15:42 -07:00
committed by GitHub
parent 744a880709
commit 31b3cc35e6
18 changed files with 46 additions and 125 deletions
+4 -14
View File
@@ -99,8 +99,10 @@ stages:
test: rhel/9.7@3.9 test: rhel/9.7@3.9
- name: RHEL 9.7 py312 - name: RHEL 9.7 py312
test: rhel/9.7@3.12 test: rhel/9.7@3.12
- name: RHEL 10.1 - name: RHEL 10.1 py312
test: rhel/10.1 test: rhel/10.1@3.12
- name: RHEL 10.1 py314
test: rhel/10.1@3.14
- name: FreeBSD 14.4 - name: FreeBSD 14.4
test: freebsd/14.4 test: freebsd/14.4
- name: FreeBSD 15.0 - name: FreeBSD 15.0
@@ -119,8 +121,6 @@ stages:
test: rhel/10.1 test: rhel/10.1
- name: FreeBSD 14.4 - name: FreeBSD 14.4
test: freebsd/14.4 test: freebsd/14.4
- name: FreeBSD 15.0
test: freebsd/15.0
groups: groups:
- 3 - 3
- 4 - 4
@@ -128,16 +128,10 @@ stages:
- template: templates/matrix.yml # context/controller (ansible-test container management) - template: templates/matrix.yml # context/controller (ansible-test container management)
parameters: parameters:
targets: targets:
- name: Alpine 3.23
test: alpine/3.23
- name: Fedora 44 - name: Fedora 44
test: fedora/44 test: fedora/44
- name: RHEL 9.7
test: rhel/9.7
- name: RHEL 10.1 - name: RHEL 10.1
test: rhel/10.1 test: rhel/10.1
- name: Ubuntu 24.04
test: ubuntu/24.04
- name: Ubuntu 26.04 - name: Ubuntu 26.04
test: ubuntu/26.04 test: ubuntu/26.04
groups: groups:
@@ -174,8 +168,6 @@ stages:
parameters: parameters:
testFormat: linux/{0} testFormat: linux/{0}
targets: targets:
- name: Alpine 3.23
test: alpine323
- name: Fedora 44 - name: Fedora 44
test: fedora44 test: fedora44
- name: Ubuntu 24.04 - name: Ubuntu 24.04
@@ -211,7 +203,6 @@ stages:
nameFormat: Python {0} nameFormat: Python {0}
testFormat: galaxy/{0}/1 testFormat: galaxy/{0}/1
targets: targets:
- test: 3.12
- test: 3.13 - test: 3.13
- test: 3.14 - test: 3.14
- test: 3.15 - test: 3.15
@@ -223,7 +214,6 @@ stages:
nameFormat: Python {0} nameFormat: Python {0}
testFormat: generic/{0}/1 testFormat: generic/{0}/1
targets: targets:
- test: 3.12
- test: 3.13 - test: 3.13
- test: 3.14 - test: 3.14
- test: 3.15 - test: 3.15
+1
View File
@@ -1,2 +1,3 @@
major_changes: major_changes:
- ansible - Add support for Python 3.15. - ansible - Add support for Python 3.15.
- ansible - Drop support for Python 3.12 on the controller.
+1 -1
View File
@@ -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, 12) _PY_MIN = (3, 13)
if sys.version_info < _PY_MIN: if sys.version_info < _PY_MIN:
raise SystemExit( raise SystemExit(
+1 -11
View File
@@ -3,7 +3,6 @@
from __future__ import annotations from __future__ import annotations
import json import json
import multiprocessing.resource_tracker
import os import os
import re import re
import sys import sys
@@ -27,19 +26,10 @@ def handle_prompt(prompt: str) -> bool:
sys.stdout.flush() sys.stdout.flush()
return True return True
# deprecated: description='Python 3.13 and later support track' python_version='3.12'
can_track = sys.version_info[:2] >= (3, 13)
kwargs = dict(track=False) if can_track else {}
# This SharedMemory instance is intentionally not closed or unlinked. # This SharedMemory instance is intentionally not closed or unlinked.
# Closing will occur naturally in the SharedMemory finalizer. # Closing will occur naturally in the SharedMemory finalizer.
# Unlinking is the responsibility of the process which created it. # Unlinking is the responsibility of the process which created it.
shm = SharedMemory(name=os.environ['_ANSIBLE_SSH_ASKPASS_SHM'], **kwargs) shm = SharedMemory(name=os.environ['_ANSIBLE_SSH_ASKPASS_SHM'], track=False)
if not can_track:
# When track=False is not available, we must unregister explicitly, since it otherwise only occurs during unlink.
# This avoids resource tracker noise on stderr during process exit.
multiprocessing.resource_tracker.unregister(shm._name, 'shared_memory')
cfg = json.loads(shm.buf.tobytes().rstrip(b'\x00')) cfg = json.loads(shm.buf.tobytes().rstrip(b'\x00'))
+1
View File
@@ -1672,6 +1672,7 @@ 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.15
- python3.14 - python3.14
- python3.13 - python3.13
- python3.12 - python3.12
+1 -2
View File
@@ -67,8 +67,7 @@ class Connection(ConnectionBase):
self.cwd = None self.cwd = None
try: try:
self.default_user = getpass.getuser() self.default_user = getpass.getuser()
except (ImportError, KeyError, OSError): except OSError:
# deprecated: description='only OSError is required for Python 3.13+' python_version='3.12'
display.vv("Current user (uid=%s) does not seem to exist on this system, leaving user empty." % os.getuid()) display.vv("Current user (uid=%s) does not seem to exist on this system, leaving user empty." % os.getuid())
self.default_user = "" self.default_user = ""
+1 -12
View File
@@ -472,8 +472,6 @@ b_NOT_SSH_ERRORS = (b'Traceback (most recent call last):', # Python-2.6 when th
SSHPASS_AVAILABLE = None SSHPASS_AVAILABLE = None
SSH_DEBUG = re.compile(r'^debug\d+: .*') SSH_DEBUG = re.compile(r'^debug\d+: .*')
_HAS_RESOURCE_TRACK = sys.version_info[:2] >= (3, 13)
PKCS11_DEFAULT_PROMPT = 'Enter PIN for ' PKCS11_DEFAULT_PROMPT = 'Enter PIN for '
SSH_ASKPASS_DEFAULT_PROMPT = 'assword' SSH_ASKPASS_DEFAULT_PROMPT = 'assword'
@@ -632,11 +630,6 @@ def _clean_resources(func):
self.shm.close() self.shm.close()
with contextlib.suppress(FileNotFoundError): with contextlib.suppress(FileNotFoundError):
self.shm.unlink() self.shm.unlink()
if not _HAS_RESOURCE_TRACK:
# deprecated: description='unneeded due to track argument for SharedMemory' python_version='3.12'
# There is a resource tracking issue where the resource is deleted, but tracking still has a record
# This will effectively overwrite the record and remove it
SharedMemory(name=self.shm.name, create=True, size=1).unlink()
return ret return ret
return inner return inner
@@ -1038,11 +1031,7 @@ class Connection(ConnectionBase):
if not conn_password: if not conn_password:
return popen_kwargs return popen_kwargs
kwargs = {} self.shm = shm = SharedMemory(create=True, size=16384, track=False)
if _HAS_RESOURCE_TRACK:
# deprecated: description='track argument for SharedMemory always available' python_version='3.12'
kwargs['track'] = False
self.shm = shm = SharedMemory(create=True, size=16384, **kwargs) # type: ignore[arg-type]
sshpass_prompt = self.get_option('sshpass_prompt') sshpass_prompt = self.get_option('sshpass_prompt')
if not sshpass_prompt and pkcs11_provider: if not sshpass_prompt and pkcs11_provider:
+1 -2
View File
@@ -174,8 +174,7 @@ class FilterUserInjector(logging.Filter):
try: try:
username = getpass.getuser() username = getpass.getuser()
except (ImportError, KeyError, OSError): except OSError:
# deprecated: description='only OSError is required for Python 3.13+' python_version='3.12'
# people like to make containers w/o actual valid passwd/shadow and use host uids # people like to make containers w/o actual valid passwd/shadow and use host uids
username = 'uid=%s' % os.getuid() username = 'uid=%s' % os.getuid()
+7 -12
View File
@@ -6,7 +6,6 @@ from __future__ import annotations
import random import random
import secrets import secrets
import string import string
import warnings
from dataclasses import dataclass from dataclasses import dataclass
@@ -19,17 +18,13 @@ PASSLIB_E = None
PASSLIB_AVAILABLE = False PASSLIB_AVAILABLE = False
try: try:
# deprecated: description='warning suppression only required for Python 3.12 and earlier' python_version='3.12' import passlib
with warnings.catch_warnings(): import passlib.hash
warnings.filterwarnings('ignore', message="'crypt' is deprecated and slated for removal in Python 3.13", category=DeprecationWarning) from passlib.utils.handlers import HasRawSalt, PrefixWrapper
try:
import passlib from passlib.utils.binary import bcrypt64
import passlib.hash except ImportError:
from passlib.utils.handlers import HasRawSalt, PrefixWrapper from passlib.utils import bcrypt64
try:
from passlib.utils.binary import bcrypt64
except ImportError:
from passlib.utils import bcrypt64
PASSLIB_AVAILABLE = True PASSLIB_AVAILABLE = True
except Exception as e: except Exception as e:
+2 -2
View File
@@ -3,7 +3,7 @@ requires = ["setuptools >= 77.0.3, <= 80.3.1"] # lower bound to support license
build-backend = "setuptools.build_meta" build-backend = "setuptools.build_meta"
[project] [project]
requires-python = ">=3.12" requires-python = ">=3.13"
name = "ansible-core" name = "ansible-core"
authors = [ authors = [
{name = "Ansible Project"}, {name = "Ansible Project"},
@@ -24,9 +24,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.12",
"Programming Language :: Python :: 3.13", "Programming Language :: Python :: 3.13",
"Programming Language :: Python :: 3.14", "Programming Language :: Python :: 3.14",
"Programming Language :: Python :: 3.15",
"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",
@@ -156,28 +156,13 @@ def get_test_scenarios() -> list[TestScenario]:
image = settings['image'] image = settings['image']
cgroup = settings.get('cgroup', 'v1-v2') cgroup = settings.get('cgroup', 'v1-v2')
if container_name == 'centos6' and os_release.id == 'alpine':
# Alpine kernels do not emulate vsyscall by default, which causes the centos6 container to fail during init.
# See: https://unix.stackexchange.com/questions/478387/running-a-centos-docker-image-on-arch-linux-exits-with-code-139
# Other distributions enable settings which trap vsyscall by default.
# See: https://www.kernelconfig.io/config_legacy_vsyscall_xonly
# See: https://www.kernelconfig.io/config_legacy_vsyscall_emulate
continue
for engine in available_engines: for engine in available_engines:
# TODO: figure out how to get tests passing using docker without disabling selinux # TODO: figure out how to get tests passing using docker without disabling selinux
disable_selinux = os_release.id == 'fedora' and engine == 'docker' and cgroup != 'none' disable_selinux = os_release.id == 'fedora' and engine == 'docker' and cgroup != 'none'
debug_systemd = cgroup != 'none' debug_systemd = cgroup != 'none'
# The sleep+pkill used to support the cgroup probe causes problems with the centos6 container. if engine == 'docker' and container_name.startswith('alpine'):
# It results in sshd connections being refused or reset for many, but not all, container instances. continue # TODO: restore Docker testing of Alpine once it's able to be used as a controller again (probably Alpine 3.24)
# The underlying cause of this issue is unknown.
probe_cgroups = container_name != 'centos6'
# The default RHEL 9 crypto policy prevents use of SHA-1.
# This results in SSH errors with centos6 containers: ssh_dispatch_run_fatal: Connection to 1.2.3.4 port 22: error in libcrypto
# See: https://access.redhat.com/solutions/6816771
enable_sha1 = os_release.id == 'rhel' and os_release.version_id.startswith('9.') and container_name == 'centos6'
# The AppArmor policy for pasta on Ubuntu 26.04 prevents podman from stopping containers. # The AppArmor policy for pasta on Ubuntu 26.04 prevents podman from stopping containers.
# Attempting to do so fails with an error like: # Attempting to do so fails with an error like:
@@ -237,9 +222,7 @@ def get_test_scenarios() -> list[TestScenario]:
image=image, image=image,
disable_selinux=disable_selinux, disable_selinux=disable_selinux,
expose_cgroup_version=expose_cgroup_version, expose_cgroup_version=expose_cgroup_version,
enable_sha1=enable_sha1,
debug_systemd=debug_systemd, debug_systemd=debug_systemd,
probe_cgroups=probe_cgroups,
disable_apparmor_profile_pasta=disable_apparmor_profile_pasta, disable_apparmor_profile_pasta=disable_apparmor_profile_pasta,
) )
) )
@@ -255,16 +238,19 @@ def run_test(scenario: TestScenario) -> TestResult:
integration = ['ansible-test', 'integration', 'split'] integration = ['ansible-test', 'integration', 'split']
integration_options = ['--target', f'docker:{scenario.container_name}', '--color', '--truncate', '0', '-v'] integration_options = ['--target', f'docker:{scenario.container_name}', '--color', '--truncate', '0', '-v']
target_only_options = []
if scenario.debug_systemd: if scenario.debug_systemd:
integration_options.append('--dev-systemd-debug') integration_options.append('--dev-systemd-debug')
if scenario.probe_cgroups: target_only_options = ['--dev-probe-cgroups', str(LOG_PATH)]
target_only_options = ['--dev-probe-cgroups', str(LOG_PATH)]
entries = get_container_completion_entries() entries = get_container_completion_entries()
alpine_container = [name for name in entries if name.startswith('alpine')][0]
# For the split test, Alpine Linux is preferred as the controller. There are two reasons for this:
# 1) It doesn't require the cgroup v1 hack, so we can test a target that doesn't need that.
# 2) It doesn't require disabling selinux, so we can test a target that doesn't need that.
# Unfortunately, this isn't always possible, such as when an Alpine release isn't available with support for controller Python versions.
controller_container = [name for name in entries if name.startswith('base')][0]
commands = [ commands = [
# The cgroup probe is only performed for the first test of the target. # The cgroup probe is only performed for the first test of the target.
@@ -272,10 +258,7 @@ def run_test(scenario: TestScenario) -> TestResult:
# The controller will be tested separately as a target. # The controller will be tested separately as a target.
# This ensures that both the probe and no-probe code paths are functional. # This ensures that both the probe and no-probe code paths are functional.
[*integration, *integration_options, *target_only_options], [*integration, *integration_options, *target_only_options],
# For the split test we'll use Alpine Linux as the controller. There are two reasons for this: [*integration, '--controller', f'docker:{controller_container}', *integration_options],
# 1) It doesn't require the cgroup v1 hack, so we can test a target that doesn't need that.
# 2) It doesn't require disabling selinux, so we can test a target that doesn't need that.
[*integration, '--controller', f'docker:{alpine_container}', *integration_options],
] ]
common_env: dict[str, str] = {} common_env: dict[str, str] = {}
@@ -332,9 +315,6 @@ def run_test(scenario: TestScenario) -> TestResult:
if scenario.disable_selinux: if scenario.disable_selinux:
run_command('setenforce', 'permissive') run_command('setenforce', 'permissive')
if scenario.enable_sha1:
run_command('update-crypto-policies', '--set', 'DEFAULT:SHA1')
if scenario.disable_apparmor_profile_pasta: if scenario.disable_apparmor_profile_pasta:
os.symlink('/etc/apparmor.d/usr.bin.pasta', '/etc/apparmor.d/disable/usr.bin.pasta') os.symlink('/etc/apparmor.d/usr.bin.pasta', '/etc/apparmor.d/disable/usr.bin.pasta')
run_command('apparmor_parser', '-R', '/etc/apparmor.d/usr.bin.pasta') run_command('apparmor_parser', '-R', '/etc/apparmor.d/usr.bin.pasta')
@@ -365,9 +345,6 @@ def run_test(scenario: TestScenario) -> TestResult:
os.unlink('/etc/apparmor.d/disable/usr.bin.pasta') os.unlink('/etc/apparmor.d/disable/usr.bin.pasta')
run_command('apparmor_parser', '/etc/apparmor.d/usr.bin.pasta') run_command('apparmor_parser', '/etc/apparmor.d/usr.bin.pasta')
if scenario.enable_sha1:
run_command('update-crypto-policies', '--set', 'DEFAULT')
if scenario.disable_selinux: if scenario.disable_selinux:
run_command('setenforce', 'enforcing') run_command('setenforce', 'enforcing')
@@ -621,9 +598,7 @@ class TestScenario:
image: str image: str
disable_selinux: bool disable_selinux: bool
expose_cgroup_version: int | None expose_cgroup_version: int | None
enable_sha1: bool
debug_systemd: bool debug_systemd: bool
probe_cgroups: bool
disable_apparmor_profile_pasta: bool disable_apparmor_profile_pasta: bool
@property @property
@@ -642,9 +617,6 @@ class TestScenario:
if self.expose_cgroup_version is not None: if self.expose_cgroup_version is not None:
tags.append(f'cgroup: {self.expose_cgroup_version}') tags.append(f'cgroup: {self.expose_cgroup_version}')
if self.enable_sha1:
tags.append('sha1: enabled')
if self.disable_apparmor_profile_pasta: if self.disable_apparmor_profile_pasta:
tags.append('apparmor(pasta): disabled') tags.append('apparmor(pasta): disabled')
@@ -9,7 +9,7 @@ macos/26.3 python=3.14 python_dir=/usr/local/bin become=sudo provider=mac arch=a
macos python_dir=/usr/local/bin become=sudo provider=mac arch=aarch64 macos python_dir=/usr/local/bin become=sudo provider=mac arch=aarch64
rhel/8.10 python=3.12 become=sudo provider=aws arch=x86_64 alias=rhel/8 rhel/8.10 python=3.12 become=sudo provider=aws arch=x86_64 alias=rhel/8
rhel/9.7 python=3.9,3.12 become=sudo provider=aws arch=x86_64 alias=rhel/9 rhel/9.7 python=3.9,3.12 become=sudo provider=aws arch=x86_64 alias=rhel/9
rhel/10.1 python=3.12 become=sudo provider=aws arch=x86_64 alias=rhel/10,rhel/latest rhel/10.1 python=3.12,3.14 become=sudo provider=aws arch=x86_64 alias=rhel/10,rhel/latest
rhel become=sudo provider=aws arch=x86_64 rhel become=sudo provider=aws arch=x86_64
ubuntu/24.04 python=3.12 become=sudo provider=aws arch=x86_64 ubuntu/24.04 python=3.12 become=sudo provider=aws arch=x86_64
ubuntu/26.04 python=3.14 become=sudo provider=aws arch=x86_64 alias=ubuntu/latest ubuntu/26.04 python=3.14 become=sudo provider=aws arch=x86_64 alias=ubuntu/latest
@@ -8,10 +8,10 @@ REMOTE_ONLY_PYTHON_VERSIONS = (
'3.9', '3.9',
'3.10', '3.10',
'3.11', '3.11',
'3.12',
) )
CONTROLLER_PYTHON_VERSIONS = ( CONTROLLER_PYTHON_VERSIONS = (
'3.12',
'3.13', '3.13',
'3.14', '3.14',
'3.15', '3.15',
@@ -305,11 +305,6 @@ bootstrap_remote_freebsd()
cryptography_pkg="" # not available cryptography_pkg="" # not available
pyyaml_pkg="" # not available pyyaml_pkg="" # not available
;; ;;
"15.0/3.12")
jinja2_pkg="" # not available
cryptography_pkg="" # not available
pyyaml_pkg="" # not available
;;
*) *)
# just assume nothing is available # just assume nothing is available
jinja2_pkg="" # not available jinja2_pkg="" # not available
@@ -429,7 +424,11 @@ bootstrap_remote_rhel_10()
{ {
optimize_dnf optimize_dnf
py_pkg_prefix="python3" if [ "${python_version}" = "3.12" ]; then
py_pkg_prefix="python3"
else
py_pkg_prefix="python${python_version}"
fi
packages=" packages="
gcc gcc
@@ -437,14 +436,13 @@ bootstrap_remote_rhel_10()
${py_pkg_prefix}-pip ${py_pkg_prefix}-pip
" "
# Jinja2, packaging and resolvelib are missing for controller supported Python versions, so we just
# skip them and let ansible-test install them from PyPI.
if [ "${controller}" ]; then if [ "${controller}" ]; then
packages=" packages="
${packages} ${packages}
${py_pkg_prefix}-cryptography ${py_pkg_prefix}-cryptography
${py_pkg_prefix}-jinja2
${py_pkg_prefix}-packaging
${py_pkg_prefix}-pyyaml ${py_pkg_prefix}-pyyaml
${py_pkg_prefix}-resolvelib
" "
fi fi
-1
View File
@@ -65,7 +65,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.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
lib/ansible/_internal/_wrapt.py mypy-3.15!skip # vendored code lib/ansible/_internal/_wrapt.py mypy-3.15!skip # vendored code
+2 -8
View File
@@ -18,15 +18,9 @@
from __future__ import annotations from __future__ import annotations
import warnings
try: try:
# deprecated: description='warning suppression only required for Python 3.12 and earlier' python_version='3.12' import passlib
with warnings.catch_warnings(): from passlib.handlers import pbkdf2
warnings.filterwarnings('ignore', message="'crypt' is deprecated and slated for removal in Python 3.13", category=DeprecationWarning)
import passlib
from passlib.handlers import pbkdf2
except ImportError: # pragma: nocover except ImportError: # pragma: nocover
passlib = None passlib = None
pbkdf2 = None pbkdf2 = None
+4 -4
View File
@@ -1,5 +1,5 @@
bcrypt < 5 ; python_version >= '3.12' # controller only, bcrypt 5+ not compatible with passlib bcrypt < 5 ; python_version >= '3.13' # controller only, bcrypt 5+ not compatible with passlib
passlib ; python_version >= '3.12' # controller only passlib ; python_version >= '3.13' # controller only
pexpect ; python_version >= '3.12' # controller only pexpect ; python_version >= '3.13' # controller only
pywinrm ; python_version >= '3.12' # controller only pywinrm ; python_version >= '3.13' # 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)
+1 -7
View File
@@ -3,8 +3,6 @@
from __future__ import annotations from __future__ import annotations
import warnings
import pytest import pytest
from pytest_mock import MockerFixture from pytest_mock import MockerFixture
@@ -228,11 +226,7 @@ def test_random_salt():
def test_passlib_bcrypt_salt(recwarn): def test_passlib_bcrypt_salt(recwarn):
# deprecated: description='warning suppression only required for Python 3.12 and earlier' python_version='3.12' passlib_exc = pytest.importorskip("passlib.exc")
with warnings.catch_warnings():
warnings.filterwarnings('ignore', message="'crypt' is deprecated and slated for removal in Python 3.13", category=DeprecationWarning)
passlib_exc = pytest.importorskip("passlib.exc")
secret = 'foo' secret = 'foo'
salt = '1234567890123456789012' salt = '1234567890123456789012'