Skip to content

Commit 101959b

Browse files
committed
Adds support for Ansible 12, Ansible 13 and Python 3.14
1 parent 73a4536 commit 101959b

12 files changed

Lines changed: 268 additions & 879 deletions

File tree

.github/workflows/python-pr.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ jobs:
88
runs-on: ubuntu-latest
99
strategy:
1010
matrix:
11-
python-version: [3.9, '3.10', '3.11', '3.12', '3.13']
11+
python-version: [3.9, '3.10', '3.11', '3.12', '3.13', '3.14']
1212

1313
steps:
1414
- uses: actions/checkout@v4

.github/workflows/python-tox.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ jobs:
88
runs-on: ubuntu-latest
99
strategy:
1010
matrix:
11-
python-version: [3.9, '3.10', '3.11', '3.12', '3.13']
11+
python-version: [3.9, '3.10', '3.11', '3.12', '3.13', '3.14']
1212

1313
steps:
1414
- uses: actions/checkout@v4

.pre-commit-config.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,5 +45,6 @@ repos:
4545
exclude: '.*'
4646
always_run: true
4747
pass_filenames: false
48+
language_version: python3.9
4849
args: ["-p", "suitable", "-p", "tests"]
4950

CHANGELOG.rst

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
Changelog
22
---------
33

4+
- Adds support for Ansible 12, Ansible 13 and Python 3.14
5+
[Daverball]
6+
47
0.22.0-rc0 (2025-09-04)
58
~~~~~~~~~~~~~~~~~~~~~~~
69

pyproject.toml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -210,6 +210,7 @@ envlist =
210210
py{310,311,312}-ansible10
211211
py{311,312,313}-ansible11
212212
py{311,312,313}-ansible12
213+
py{312,313,314}-ansible13
213214
ruff
214215
bandit
215216
mypy
@@ -222,6 +223,7 @@ python =
222223
3.11: py311,ruff,bandit,mypy
223224
3.12: py312
224225
3.13: py313
226+
3.14: py314
225227
226228
[testenv]
227229
usedevelop = true
@@ -242,6 +244,8 @@ deps =
242244
ansible11: ansible-core==2.18.*
243245
ansible12: ansible==12.*
244246
ansible12: ansible-core==2.19.*
247+
ansible13: ansible==13.*
248+
ansible13: ansible-core==2.20.*
245249
246250
commands = pytest --cov --cov-report= {posargs}
247251
@@ -276,6 +280,7 @@ commands =
276280
mypy -p suitable -p tests --python-version 3.11
277281
mypy -p suitable -p tests --python-version 3.12
278282
mypy -p suitable -p tests --python-version 3.13
283+
mypy -p suitable -p tests --python-version 3.14
279284
280285
[testenv:report]
281286
deps =

scripts/generate_module_hints.py

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99

1010
import os
1111
import re
12+
import textwrap
1213

1314
from ansible import constants as C # type:ignore # noqa: N812
1415
from ansible.plugins.loader import fragment_loader # type:ignore
@@ -81,6 +82,8 @@ def write_function_parameter_list(
8182
name = 'async_'
8283
# if the type is not set it appears to always be str
8384
type_name = type_map.get(meta.get('type', 'string'), 'Incomplete')
85+
if not isinstance(type_name, str):
86+
raise ValueError(type_name)
8487
if print_default := (
8588
type_name not in ('Mapping', 'Sequence', 'Incomplete')
8689
and 'default' in meta
@@ -90,9 +93,11 @@ def write_function_parameter_list(
9093
default = default in ('yes', True)
9194

9295
default_type = type(default).__name__
96+
if default_type.startswith('_AnsibleTagged'):
97+
default_type = default_type[14:].lower()
9398
if default_type == 'AnsibleUnicode':
9499
default_type = 'str'
95-
elif default_type == 'AnsibleSequence':
100+
elif default_type in ('AnsibleSequence', 'list'):
96101
default_type = 'str'
97102
# we don't want to render this
98103
default = ' '
@@ -371,11 +376,19 @@ def write_function_docstring(options: dict[str, Any] | None) -> None:
371376

372377
def write_return_type(returns: dict[str, Any] | None) -> None:
373378
assert RunnerResults.__doc__
379+
# NOTE: Starting with Python 3.13 docstrings are automatically
380+
# dedented and stripped like this, so if we want consistent
381+
# output between versions we need to pre-process the docstring
382+
doc = textwrap.dedent(RunnerResults.__doc__).strip('\r\n')
374383
types_py.write(
375384
'\n\n@type_check_only\n'
376385
f'class {return_type_name}(RunnerResults):\n'
377-
f' """{RunnerResults.__doc__[:-4]}'
386+
f' """\n'
378387
)
388+
for line in doc.splitlines():
389+
types_py.write(f' {line}\n' if line else '\n')
390+
types_py.write('\n')
391+
379392
if not returns:
380393
comment = (
381394
'The return values for this module were not '

setup.cfg

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ classifiers =
2323
Programming Language :: Python :: 3.11
2424
Programming Language :: Python :: 3.12
2525
Programming Language :: Python :: 3.13
26+
Programming Language :: Python :: 3.14
2627
Programming Language :: Python :: Implementation :: CPython
2728
Topic :: Software Development :: Libraries :: Python Modules
2829

src/suitable/_module_types.py

Lines changed: 12 additions & 131 deletions
Original file line numberDiff line numberDiff line change
@@ -255,17 +255,17 @@ def ansible_job_id(self, server: str | None = None) -> str:
255255
"""
256256
return self.acquire(server, 'ansible_job_id')
257257

258-
def finished(self, server: str | None = None) -> int:
258+
def finished(self, server: str | None = None) -> bool:
259259
"""
260-
Whether the asynchronous job has finished (``1``) or not (``0``).
260+
Whether the asynchronous job has finished or not.
261261
262262
Returned when: always
263263
"""
264264
return self.acquire(server, 'finished')
265265

266-
def started(self, server: str | None = None) -> int:
266+
def started(self, server: str | None = None) -> bool:
267267
"""
268-
Whether the asynchronous job has started (``1``) or not (``0``).
268+
Whether the asynchronous job has started or not.
269269
270270
Returned when: always
271271
"""
@@ -1093,15 +1093,6 @@ def remote_url_changed(self, server: str | None = None) -> bool:
10931093
"""
10941094
return self.acquire(server, 'remote_url_changed')
10951095

1096-
def warnings(self, server: str | None = None) -> str:
1097-
"""
1098-
List of warnings if requested features were not available due to a too
1099-
old git version.
1100-
1101-
Returned when: error
1102-
"""
1103-
return self.acquire(server, 'warnings')
1104-
11051096
def git_dir_now(self, server: str | None = None) -> str:
11061097
"""
11071098
Contains the new path of .git directory if it is changed.
@@ -1262,25 +1253,6 @@ class ImportTasksResults(RunnerResults):
12621253
"""
12631254

12641255

1265-
@type_check_only
1266-
class IncludeResults(RunnerResults):
1267-
"""
1268-
Wraps the results of parsed module_runner output.
1269-
1270-
The result may be used just like it is in Ansible::
1271-
1272-
result['contacted']['server']['rc']
1273-
1274-
or it can alternatively be used thusly::
1275-
1276-
result.rc('server')
1277-
1278-
The return values for this module were not documented when these types
1279-
were auto-generated, this could mean there are no return values.
1280-
1281-
"""
1282-
1283-
12841256
@type_check_only
12851257
class IncludeRoleResults(RunnerResults):
12861258
"""
@@ -2720,25 +2692,6 @@ def elapsed(self, server: str | None = None) -> float:
27202692
return self.acquire(server, 'elapsed')
27212693

27222694

2723-
@type_check_only
2724-
class YumResults(RunnerResults):
2725-
"""
2726-
Wraps the results of parsed module_runner output.
2727-
2728-
The result may be used just like it is in Ansible::
2729-
2730-
result['contacted']['server']['rc']
2731-
2732-
or it can alternatively be used thusly::
2733-
2734-
result.rc('server')
2735-
2736-
The return values for this module were not documented when these types
2737-
were auto-generated, this could mean there are no return values.
2738-
2739-
"""
2740-
2741-
27422695
@type_check_only
27432696
class YumRepositoryResults(RunnerResults):
27442697
"""
@@ -4411,78 +4364,6 @@ def zone(self, server: str | None = None) -> dict[str, Incomplete]:
44114364
return self.acquire(server, 'zone')
44124365

44134366

4414-
@type_check_only
4415-
class WinDomainResults(RunnerResults):
4416-
"""
4417-
Wraps the results of parsed module_runner output.
4418-
4419-
The result may be used just like it is in Ansible::
4420-
4421-
result['contacted']['server']['rc']
4422-
4423-
or it can alternatively be used thusly::
4424-
4425-
result.rc('server')
4426-
4427-
"""
4428-
4429-
def reboot_required(self, server: str | None = None) -> bool:
4430-
"""
4431-
True if changes were made that require a reboot.
4432-
4433-
Returned when: always
4434-
"""
4435-
return self.acquire(server, 'reboot_required')
4436-
4437-
4438-
@type_check_only
4439-
class WinDomainControllerResults(RunnerResults):
4440-
"""
4441-
Wraps the results of parsed module_runner output.
4442-
4443-
The result may be used just like it is in Ansible::
4444-
4445-
result['contacted']['server']['rc']
4446-
4447-
or it can alternatively be used thusly::
4448-
4449-
result.rc('server')
4450-
4451-
"""
4452-
4453-
def reboot_required(self, server: str | None = None) -> bool:
4454-
"""
4455-
True if changes were made that require a reboot.
4456-
4457-
Returned when: always
4458-
"""
4459-
return self.acquire(server, 'reboot_required')
4460-
4461-
4462-
@type_check_only
4463-
class WinDomainMembershipResults(RunnerResults):
4464-
"""
4465-
Wraps the results of parsed module_runner output.
4466-
4467-
The result may be used just like it is in Ansible::
4468-
4469-
result['contacted']['server']['rc']
4470-
4471-
or it can alternatively be used thusly::
4472-
4473-
result.rc('server')
4474-
4475-
"""
4476-
4477-
def reboot_required(self, server: str | None = None) -> bool:
4478-
"""
4479-
True if changes were made that require a reboot.
4480-
4481-
Returned when: always
4482-
"""
4483-
return self.acquire(server, 'reboot_required')
4484-
4485-
44864367
@type_check_only
44874368
class WinDscResults(RunnerResults):
44884369
"""
@@ -4565,18 +4446,18 @@ def value(self, server: str | None = None) -> str:
45654446
"""
45664447
return self.acquire(server, 'value')
45674448

4568-
def values( # type:ignore[override]
4569-
self,
4570-
server: str | None = None
4571-
) -> dict[str, Incomplete]:
4449+
def env_values(self, server: str | None = None) -> dict[str, Incomplete]:
45724450
"""
4573-
dictionary of before and after values; each key is a variable name,
4451+
Dictionary of before and after values; each key is a variable name,
45744452
each value is another dict with ``before``, ``after``, and ``changed``
45754453
keys.
45764454
4455+
This is also returned under the ``values`` key for backward
4456+
compatibility, but that usage is deprecated.
4457+
45774458
Returned when: always
45784459
"""
4579-
return self.acquire(server, 'values')
4460+
return self.acquire(server, 'env_values')
45804461

45814462

45824463
@type_check_only
@@ -4684,7 +4565,7 @@ def exitcode(self, server: str | None = None) -> str:
46844565
"""
46854566
return self.acquire(server, 'exitcode')
46864567

4687-
def feature_result(self, server: str | None = None) -> Incomplete:
4568+
def feature_result(self, server: str | None = None) -> list[Incomplete]:
46884569
"""
46894570
List of features that were installed or removed.
46904571
@@ -6117,7 +5998,7 @@ def filtered_updates(
61175998
"""
61185999
Updates that were found but were filtered based on *blacklist*,
61196000
*whitelist* or *category_names*. The return value is in the same form
6120-
as *updates*, along with *filtered_reason*.
6001+
as *updates*.
61216002
61226003
Returned when: success
61236004
"""

0 commit comments

Comments
 (0)