ansible-core 2.19.3rc1__py3-none-any.whl → 2.20.0b2__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of ansible-core might be problematic. Click here for more details.

Files changed (201) hide show
  1. ansible/_internal/__init__.py +1 -4
  2. ansible/_internal/_ansiballz/_builder.py +1 -3
  3. ansible/_internal/_collection_proxy.py +7 -9
  4. ansible/_internal/_json/__init__.py +3 -4
  5. ansible/_internal/_templating/_engine.py +1 -1
  6. ansible/_internal/_templating/_jinja_plugins.py +1 -2
  7. ansible/_internal/_wrapt.py +105 -301
  8. ansible/cli/__init__.py +11 -10
  9. ansible/cli/adhoc.py +1 -2
  10. ansible/cli/arguments/option_helpers.py +1 -1
  11. ansible/cli/config.py +5 -6
  12. ansible/cli/doc.py +70 -68
  13. ansible/cli/galaxy.py +15 -24
  14. ansible/cli/inventory.py +0 -1
  15. ansible/cli/playbook.py +0 -1
  16. ansible/cli/pull.py +0 -1
  17. ansible/cli/scripts/ansible_connection_cli_stub.py +1 -1
  18. ansible/collections/list.py +4 -2
  19. ansible/config/base.yml +1 -25
  20. ansible/config/manager.py +0 -2
  21. ansible/executor/play_iterator.py +42 -20
  22. ansible/executor/playbook_executor.py +0 -9
  23. ansible/executor/task_executor.py +26 -18
  24. ansible/executor/task_queue_manager.py +1 -3
  25. ansible/galaxy/api.py +33 -80
  26. ansible/galaxy/collection/__init__.py +4 -17
  27. ansible/galaxy/dependency_resolution/dataclasses.py +0 -10
  28. ansible/galaxy/dependency_resolution/providers.py +24 -118
  29. ansible/galaxy/role.py +1 -33
  30. ansible/inventory/manager.py +2 -3
  31. ansible/keyword_desc.yml +0 -3
  32. ansible/module_utils/_internal/_datatag/__init__.py +2 -10
  33. ansible/module_utils/_internal/_no_six.py +86 -0
  34. ansible/module_utils/_text.py +28 -8
  35. ansible/module_utils/ansible_release.py +2 -2
  36. ansible/module_utils/basic.py +26 -23
  37. ansible/module_utils/common/_collections_compat.py +11 -2
  38. ansible/module_utils/common/collections.py +8 -3
  39. ansible/module_utils/common/dict_transformations.py +1 -2
  40. ansible/module_utils/common/network.py +4 -2
  41. ansible/module_utils/common/parameters.py +32 -41
  42. ansible/module_utils/common/text/converters.py +109 -23
  43. ansible/module_utils/common/text/formatters.py +6 -2
  44. ansible/module_utils/common/validation.py +11 -9
  45. ansible/module_utils/connection.py +8 -3
  46. ansible/module_utils/facts/hardware/linux.py +23 -7
  47. ansible/module_utils/facts/hardware/netbsd.py +1 -1
  48. ansible/module_utils/facts/hardware/sunos.py +2 -1
  49. ansible/module_utils/facts/packages.py +6 -2
  50. ansible/module_utils/facts/system/distribution.py +2 -1
  51. ansible/module_utils/facts/system/env.py +6 -3
  52. ansible/module_utils/facts/system/local.py +3 -1
  53. ansible/module_utils/parsing/convert_bool.py +6 -2
  54. ansible/module_utils/service.py +2 -3
  55. ansible/module_utils/six/__init__.py +19 -6
  56. ansible/module_utils/yumdnf.py +0 -5
  57. ansible/modules/apt.py +18 -13
  58. ansible/modules/apt_repository.py +1 -1
  59. ansible/modules/assemble.py +5 -9
  60. ansible/modules/blockinfile.py +39 -23
  61. ansible/modules/cron.py +26 -35
  62. ansible/modules/deb822_repository.py +83 -12
  63. ansible/modules/dnf.py +3 -7
  64. ansible/modules/dnf5.py +4 -6
  65. ansible/modules/expect.py +0 -3
  66. ansible/modules/find.py +1 -2
  67. ansible/modules/get_url.py +1 -1
  68. ansible/modules/git.py +4 -5
  69. ansible/modules/include_vars.py +1 -1
  70. ansible/modules/known_hosts.py +7 -1
  71. ansible/modules/lineinfile.py +71 -63
  72. ansible/modules/package_facts.py +1 -1
  73. ansible/modules/pip.py +8 -2
  74. ansible/modules/replace.py +6 -6
  75. ansible/modules/service.py +3 -4
  76. ansible/modules/stat.py +20 -0
  77. ansible/modules/uri.py +9 -10
  78. ansible/modules/user.py +1 -2
  79. ansible/modules/wait_for.py +2 -2
  80. ansible/modules/wait_for_connection.py +2 -1
  81. ansible/modules/yum_repository.py +1 -16
  82. ansible/parsing/dataloader.py +24 -31
  83. ansible/parsing/mod_args.py +3 -0
  84. ansible/parsing/vault/__init__.py +1 -2
  85. ansible/playbook/base.py +8 -56
  86. ansible/playbook/block.py +1 -63
  87. ansible/playbook/collectionsearch.py +1 -2
  88. ansible/playbook/handler.py +1 -7
  89. ansible/playbook/helpers.py +15 -20
  90. ansible/playbook/included_file.py +1 -1
  91. ansible/playbook/play.py +105 -49
  92. ansible/playbook/play_context.py +4 -0
  93. ansible/playbook/role/__init__.py +10 -65
  94. ansible/playbook/role/definition.py +3 -4
  95. ansible/playbook/role/include.py +2 -3
  96. ansible/playbook/role/metadata.py +1 -12
  97. ansible/playbook/role/requirement.py +1 -2
  98. ansible/playbook/role_include.py +1 -2
  99. ansible/playbook/taggable.py +16 -5
  100. ansible/playbook/task.py +51 -55
  101. ansible/plugins/action/__init__.py +20 -19
  102. ansible/plugins/action/add_host.py +1 -2
  103. ansible/plugins/action/fetch.py +3 -5
  104. ansible/plugins/action/group_by.py +1 -2
  105. ansible/plugins/action/include_vars.py +20 -22
  106. ansible/plugins/action/script.py +1 -3
  107. ansible/plugins/action/template.py +1 -2
  108. ansible/plugins/action/uri.py +4 -2
  109. ansible/plugins/cache/__init__.py +1 -0
  110. ansible/plugins/callback/__init__.py +13 -6
  111. ansible/plugins/connection/__init__.py +3 -7
  112. ansible/plugins/connection/local.py +2 -3
  113. ansible/plugins/connection/psrp.py +0 -2
  114. ansible/plugins/connection/ssh.py +2 -7
  115. ansible/plugins/connection/winrm.py +0 -2
  116. ansible/plugins/doc_fragments/result_format_callback.py +15 -0
  117. ansible/plugins/filter/core.py +4 -5
  118. ansible/plugins/filter/encryption.py +3 -27
  119. ansible/plugins/filter/mathstuff.py +1 -2
  120. ansible/plugins/filter/to_nice_yaml.yml +31 -3
  121. ansible/plugins/filter/to_yaml.yml +29 -12
  122. ansible/plugins/inventory/__init__.py +1 -2
  123. ansible/plugins/inventory/toml.py +3 -6
  124. ansible/plugins/inventory/yaml.py +1 -2
  125. ansible/plugins/loader.py +3 -4
  126. ansible/plugins/lookup/password.py +1 -2
  127. ansible/plugins/lookup/subelements.py +2 -3
  128. ansible/plugins/lookup/url.py +1 -1
  129. ansible/plugins/lookup/varnames.py +1 -2
  130. ansible/plugins/shell/__init__.py +9 -4
  131. ansible/plugins/shell/powershell.py +8 -24
  132. ansible/plugins/strategy/__init__.py +6 -3
  133. ansible/plugins/test/core.py +4 -1
  134. ansible/plugins/test/falsy.yml +1 -1
  135. ansible/plugins/test/regex.yml +18 -6
  136. ansible/plugins/test/truthy.yml +1 -1
  137. ansible/release.py +2 -2
  138. ansible/template/__init__.py +3 -7
  139. ansible/utils/collection_loader/_collection_config.py +5 -0
  140. ansible/utils/collection_loader/_collection_finder.py +11 -14
  141. ansible/utils/context_objects.py +7 -4
  142. ansible/utils/display.py +7 -6
  143. ansible/utils/encrypt.py +0 -5
  144. ansible/utils/helpers.py +6 -2
  145. ansible/utils/jsonrpc.py +7 -3
  146. ansible/utils/plugin_docs.py +49 -38
  147. ansible/utils/ssh_functions.py +0 -19
  148. ansible/utils/unsafe_proxy.py +7 -7
  149. ansible/vars/clean.py +2 -3
  150. ansible/vars/manager.py +28 -22
  151. ansible/vars/plugins.py +1 -31
  152. {ansible_core-2.19.3rc1.dist-info → ansible_core-2.20.0b2.dist-info}/METADATA +3 -3
  153. {ansible_core-2.19.3rc1.dist-info → ansible_core-2.20.0b2.dist-info}/RECORD +199 -200
  154. ansible_test/_data/completion/docker.txt +7 -7
  155. ansible_test/_data/completion/network.txt +0 -1
  156. ansible_test/_data/completion/remote.txt +4 -4
  157. ansible_test/_data/requirements/ansible-test.txt +1 -1
  158. ansible_test/_data/requirements/sanity.changelog.txt +1 -1
  159. ansible_test/_data/requirements/sanity.pep8.txt +1 -1
  160. ansible_test/_data/requirements/sanity.pylint.txt +4 -4
  161. ansible_test/_internal/cache.py +2 -5
  162. ansible_test/_internal/cli/compat.py +1 -1
  163. ansible_test/_internal/commands/coverage/combine.py +1 -3
  164. ansible_test/_internal/commands/integration/__init__.py +3 -7
  165. ansible_test/_internal/commands/integration/cloud/httptester.py +1 -1
  166. ansible_test/_internal/commands/integration/coverage.py +1 -3
  167. ansible_test/_internal/commands/integration/filters.py +5 -10
  168. ansible_test/_internal/commands/sanity/validate_modules.py +1 -5
  169. ansible_test/_internal/commands/units/__init__.py +1 -13
  170. ansible_test/_internal/completion.py +2 -5
  171. ansible_test/_internal/config.py +2 -7
  172. ansible_test/_internal/coverage_util.py +1 -1
  173. ansible_test/_internal/delegation.py +2 -0
  174. ansible_test/_internal/docker_util.py +1 -1
  175. ansible_test/_internal/host_profiles.py +6 -11
  176. ansible_test/_internal/provider/__init__.py +2 -5
  177. ansible_test/_internal/provisioning.py +2 -5
  178. ansible_test/_internal/pypi_proxy.py +1 -1
  179. ansible_test/_internal/target.py +2 -6
  180. ansible_test/_internal/thread.py +1 -4
  181. ansible_test/_internal/util.py +9 -14
  182. ansible_test/_util/controller/sanity/code-smell/runtime-metadata.py +14 -19
  183. ansible_test/_util/controller/sanity/pylint/plugins/unwanted.py +40 -27
  184. ansible_test/_util/controller/sanity/validate-modules/validate_modules/main.py +31 -18
  185. ansible_test/_util/controller/sanity/validate-modules/validate_modules/module_args.py +1 -2
  186. ansible_test/_util/controller/sanity/validate-modules/validate_modules/schema.py +59 -71
  187. ansible_test/_util/controller/sanity/validate-modules/validate_modules/utils.py +1 -2
  188. ansible_test/_util/target/cli/ansible_test_cli_stub.py +4 -2
  189. ansible_test/_util/target/common/constants.py +2 -2
  190. ansible_test/_util/target/setup/bootstrap.sh +0 -6
  191. ansible/utils/py3compat.py +0 -27
  192. ansible_test/_data/pytest/config/legacy.ini +0 -4
  193. {ansible_core-2.19.3rc1.dist-info → ansible_core-2.20.0b2.dist-info}/WHEEL +0 -0
  194. {ansible_core-2.19.3rc1.dist-info → ansible_core-2.20.0b2.dist-info}/entry_points.txt +0 -0
  195. {ansible_core-2.19.3rc1.dist-info → ansible_core-2.20.0b2.dist-info}/licenses/COPYING +0 -0
  196. {ansible_core-2.19.3rc1.dist-info → ansible_core-2.20.0b2.dist-info}/licenses/licenses/Apache-License.txt +0 -0
  197. {ansible_core-2.19.3rc1.dist-info → ansible_core-2.20.0b2.dist-info}/licenses/licenses/BSD-3-Clause.txt +0 -0
  198. {ansible_core-2.19.3rc1.dist-info → ansible_core-2.20.0b2.dist-info}/licenses/licenses/MIT-license.txt +0 -0
  199. {ansible_core-2.19.3rc1.dist-info → ansible_core-2.20.0b2.dist-info}/licenses/licenses/PSF-license.txt +0 -0
  200. {ansible_core-2.19.3rc1.dist-info → ansible_core-2.20.0b2.dist-info}/licenses/licenses/simplified_bsd.txt +0 -0
  201. {ansible_core-2.19.3rc1.dist-info → ansible_core-2.20.0b2.dist-info}/top_level.txt +0 -0
@@ -192,7 +192,7 @@ class ShellModule(ShellBase):
192
192
 
193
193
  def join_path(self, *args):
194
194
  # use normpath() to remove doubled slashed and convert forward to backslashes
195
- parts = [ntpath.normpath(self._unquote(arg)) for arg in args]
195
+ parts = [ntpath.normpath(arg) for arg in args]
196
196
 
197
197
  # Because ntpath.join treats any component that begins with a backslash as an absolute path,
198
198
  # we have to strip slashes from at least the beginning, otherwise join will ignore all previous
@@ -210,7 +210,6 @@ class ShellModule(ShellBase):
210
210
 
211
211
  def path_has_trailing_slash(self, path):
212
212
  # Allow Windows paths to be specified using either slash.
213
- path = self._unquote(path)
214
213
  return path.endswith('/') or path.endswith('\\')
215
214
 
216
215
  def chmod(self, paths, mode):
@@ -223,11 +222,11 @@ class ShellModule(ShellBase):
223
222
  raise NotImplementedError('set_user_facl is not implemented for Powershell')
224
223
 
225
224
  def remove(self, path, recurse=False):
226
- path = self._escape(self._unquote(path))
225
+ quoted_path = self._escape(path)
227
226
  if recurse:
228
- return self._encode_script("""Remove-Item '%s' -Force -Recurse;""" % path)
227
+ return self._encode_script("""Remove-Item '%s' -Force -Recurse;""" % quoted_path)
229
228
  else:
230
- return self._encode_script("""Remove-Item '%s' -Force;""" % path)
229
+ return self._encode_script("""Remove-Item '%s' -Force;""" % quoted_path)
231
230
 
232
231
  def mkdtemp(
233
232
  self,
@@ -240,7 +239,6 @@ class ShellModule(ShellBase):
240
239
  # compatibility in case other action plugins outside Ansible calls this.
241
240
  if not basefile:
242
241
  basefile = self.__class__._generate_temp_dir_name()
243
- basefile = self._escape(self._unquote(basefile))
244
242
  basetmpdir = self._escape(tmpdir if tmpdir else self.get_option('remote_tmp'))
245
243
 
246
244
  script = f"""
@@ -263,7 +261,6 @@ class ShellModule(ShellBase):
263
261
  if not basefile:
264
262
  basefile = self.__class__._generate_temp_dir_name()
265
263
 
266
- basefile = self._unquote(basefile)
267
264
  basetmpdir = tmpdir if tmpdir else self.get_option('remote_tmp')
268
265
 
269
266
  script, stdin = _bootstrap_powershell_script("powershell_mkdtemp.ps1", {
@@ -283,7 +280,6 @@ class ShellModule(ShellBase):
283
280
  ) -> str:
284
281
  # This is not called in Ansible anymore but it is kept for backwards
285
282
  # compatibility in case other actions plugins outside Ansible called this.
286
- user_home_path = self._unquote(user_home_path)
287
283
  if user_home_path == '~':
288
284
  script = 'Write-Output (Get-Location).Path'
289
285
  elif user_home_path.startswith('~\\'):
@@ -297,7 +293,6 @@ class ShellModule(ShellBase):
297
293
  user_home_path: str,
298
294
  username: str = '',
299
295
  ) -> _ShellCommand:
300
- user_home_path = self._unquote(user_home_path)
301
296
  script, stdin = _bootstrap_powershell_script("powershell_expand_user.ps1", {
302
297
  'Path': user_home_path,
303
298
  })
@@ -308,7 +303,7 @@ class ShellModule(ShellBase):
308
303
  )
309
304
 
310
305
  def exists(self, path):
311
- path = self._escape(self._unquote(path))
306
+ path = self._escape(path)
312
307
  script = """
313
308
  If (Test-Path '%s')
314
309
  {
@@ -329,7 +324,7 @@ class ShellModule(ShellBase):
329
324
  version="2.23",
330
325
  help_text="Use `ActionBase._execute_remote_stat()` instead.",
331
326
  )
332
- path = self._escape(self._unquote(path))
327
+ path = self._escape(path)
333
328
  script = """
334
329
  If (Test-Path -PathType Leaf '%(path)s')
335
330
  {
@@ -364,7 +359,7 @@ class ShellModule(ShellBase):
364
359
  if arg_path:
365
360
  # Running a module without the exec_wrapper and with an argument
366
361
  # file.
367
- script_path = self._unquote(cmd_parts[0])
362
+ script_path = cmd_parts[0]
368
363
  if not script_path.lower().endswith('.ps1'):
369
364
  script_path += '.ps1'
370
365
 
@@ -387,7 +382,6 @@ class ShellModule(ShellBase):
387
382
  cmd_parts.insert(0, shebang[2:])
388
383
  elif not shebang:
389
384
  # The module is assumed to be a binary
390
- cmd_parts[0] = self._unquote(cmd_parts[0])
391
385
  cmd_parts.append(arg_path)
392
386
  script = """
393
387
  Try
@@ -428,19 +422,9 @@ class ShellModule(ShellBase):
428
422
  return self._encode_script(script, preserve_rc=False)
429
423
 
430
424
  def wrap_for_exec(self, cmd):
425
+ super().wrap_for_exec(cmd)
431
426
  return '& %s; exit $LASTEXITCODE' % cmd
432
427
 
433
- def _unquote(self, value):
434
- """Remove any matching quotes that wrap the given value."""
435
- value = to_text(value or '')
436
- m = re.match(r'^\s*?\'(.*?)\'\s*?$', value)
437
- if m:
438
- return m.group(1)
439
- m = re.match(r'^\s*?"(.*?)"\s*?$', value)
440
- if m:
441
- return m.group(1)
442
- return value
443
-
444
428
  def _escape(self, value):
445
429
  """Return value escaped for use in PowerShell single quotes."""
446
430
  # There are 5 chars that need to be escaped in a single quote.
@@ -53,6 +53,9 @@ from ansible.utils.sentinel import Sentinel
53
53
  from ansible.utils.vars import combine_vars
54
54
  from ansible.vars.clean import strip_internal_keys, module_response_deepcopy
55
55
 
56
+ if t.TYPE_CHECKING:
57
+ from ansible.playbook.role_include import IncludeRole
58
+
56
59
  display = Display()
57
60
 
58
61
  __all__ = ['StrategyBase']
@@ -581,7 +584,7 @@ class StrategyBase:
581
584
  self._variable_manager.set_nonpersistent_facts(
582
585
  original_host.name,
583
586
  dict(
584
- ansible_failed_task=original_task.serialize(),
587
+ ansible_failed_task=original_task.dump_attrs(),
585
588
  ansible_failed_result=task_result._return_data,
586
589
  ),
587
590
  )
@@ -799,7 +802,7 @@ class StrategyBase:
799
802
 
800
803
  return ret_results
801
804
 
802
- def _copy_included_file(self, included_file: IncludedFile) -> IncludedFile:
805
+ def _copy_included_file(self, included_file: IncludedFile) -> TaskInclude | IncludeRole:
803
806
  """
804
807
  A proven safe and performant way to create a copy of an included file
805
808
  """
@@ -900,7 +903,7 @@ class StrategyBase:
900
903
  display.warning("%s task does not support when conditional" % task_name)
901
904
 
902
905
  def _execute_meta(self, task: Task, play_context, iterator, target_host: Host):
903
- task.resolved_action = 'ansible.builtin.meta' # _post_validate_args is never called for meta actions, so resolved_action hasn't been set
906
+ task._resolved_action = 'ansible.builtin.meta' # _post_validate_args is never called for meta actions, so resolved_action hasn't been set
904
907
 
905
908
  # meta tasks store their args in the _raw_params field of args,
906
909
  # since they do not use k=v pairs, so get that
@@ -142,6 +142,9 @@ def regex(value='', pattern='', ignorecase=False, multiline=False, match_type='s
142
142
  This is likely only useful for `search` and `match` which already
143
143
  have their own filters.
144
144
  """
145
+ valid_match_types = ('search', 'match', 'fullmatch')
146
+ if match_type not in valid_match_types:
147
+ raise errors.AnsibleTemplatePluginError(f"Invalid match_type specified. Expected one of: {', '.join(valid_match_types)}.", obj=match_type)
145
148
  value = to_text(value, errors='surrogate_or_strict')
146
149
  flags = 0
147
150
  if ignorecase:
@@ -149,7 +152,7 @@ def regex(value='', pattern='', ignorecase=False, multiline=False, match_type='s
149
152
  if multiline:
150
153
  flags |= re.M
151
154
  _re = re.compile(pattern, flags=flags)
152
- return bool(getattr(_re, match_type, 'search')(value))
155
+ return bool(getattr(_re, match_type)(value))
153
156
 
154
157
 
155
158
  @accept_args_markers
@@ -5,7 +5,7 @@ DOCUMENTATION:
5
5
  short_description: Pythonic false
6
6
  description:
7
7
  - This check is a more Python version of what is 'false'.
8
- - It is the opposite of 'truthy'.
8
+ - It is the opposite of P(ansible.builtin.truthy#test).
9
9
  options:
10
10
  _input:
11
11
  description: An expression that can be expressed in a boolean context.
@@ -3,7 +3,7 @@ DOCUMENTATION:
3
3
  author: Ansible Core
4
4
  short_description: Does string match regular expression from the start
5
5
  description:
6
- - Compare string against regular expression using Python's match or search functions.
6
+ - Compare string against regular expression using Python's match, fullmatch or search functions.
7
7
  options:
8
8
  _input:
9
9
  description: String to match.
@@ -22,14 +22,26 @@ DOCUMENTATION:
22
22
  type: boolean
23
23
  default: False
24
24
  match_type:
25
- description: Decide which function to be used to do the matching.
25
+ description:
26
+ - Decide which function to be used to do the matching.
26
27
  type: string
27
- choices: [match, search]
28
+ choices: [match, search, fullmatch]
28
29
  default: search
29
30
 
30
- EXAMPLES: |
31
- url: "https://example.com/users/foo/resources/bar"
32
- foundmatch: url is regex("example\.com/\w+/foo")
31
+ EXAMPLES:
32
+ - name: check if string matches regex
33
+ assert:
34
+ that:
35
+ - 'url is regex("example\.com/\w+/foo")'
36
+ vars:
37
+ url: "https://example.com/users/foo/resources/bar"
38
+
39
+ - name: check if string matches regex ignoring case
40
+ assert:
41
+ that:
42
+ - 'url is regex("EXAMPLE\.COM/\w+/foo", ignorecase=True)'
43
+ vars:
44
+ url: "https://Example.com/users/foo/resources/bar"
33
45
 
34
46
  RETURN:
35
47
  _value:
@@ -20,5 +20,5 @@ EXAMPLES: |
20
20
  thisisfalse: '{{ "" is truthy }}'
21
21
  RETURN:
22
22
  _value:
23
- description: Returns V(True) if the condition is not "Python truthy", V(False) otherwise.
23
+ description: Returns V(True) if the condition is "Python truthy", V(False) otherwise.
24
24
  type: boolean
ansible/release.py CHANGED
@@ -17,6 +17,6 @@
17
17
 
18
18
  from __future__ import annotations
19
19
 
20
- __version__ = '2.19.3rc1'
20
+ __version__ = '2.20.0b2'
21
21
  __author__ = 'Ansible, Inc.'
22
- __codename__ = "What Is and What Should Never Be"
22
+ __codename__ = "Good Times Bad Times"
@@ -25,7 +25,6 @@ if _t.TYPE_CHECKING: # pragma: nocover
25
25
 
26
26
  _display: _t.Final[_Display] = _Display()
27
27
  _UNSET = _t.cast(_t.Any, object())
28
- _TTrustable = _t.TypeVar('_TTrustable', bound=str | _io.IOBase | _t.TextIO | _t.BinaryIO)
29
28
  _TRUSTABLE_TYPES = (str, _io.IOBase)
30
29
 
31
30
  AnsibleUndefined = _jinja_common.UndefinedMarker
@@ -361,7 +360,7 @@ def generate_ansible_template_vars(
361
360
  return _template_vars.generate_ansible_template_vars(path=path, fullpath=fullpath, dest_path=dest_path, include_ansible_managed=True)
362
361
 
363
362
 
364
- def trust_as_template(value: _TTrustable) -> _TTrustable:
363
+ def trust_as_template[T: str | _io.IOBase | _t.TextIO | _t.BinaryIO](value: T) -> T:
365
364
  """
366
365
  Returns `value` tagged as trusted for templating.
367
366
  Raises a `TypeError` if `value` is not a supported type.
@@ -385,10 +384,7 @@ def is_trusted_as_template(value: object) -> bool:
385
384
  return isinstance(value, _TRUSTABLE_TYPES) and _tags.TrustedAsTemplate.is_tagged_on(value)
386
385
 
387
386
 
388
- _TCallable = _t.TypeVar('_TCallable', bound=_t.Callable)
389
-
390
-
391
- def accept_args_markers(plugin: _TCallable) -> _TCallable:
387
+ def accept_args_markers[T: _t.Callable](plugin: T) -> T:
392
388
  """
393
389
  A decorator to mark a Jinja plugin as capable of handling `Marker` values for its top-level arguments.
394
390
  Non-decorated plugin invocation is skipped when a top-level argument is a `Marker`, with the first such value substituted as the plugin result.
@@ -399,7 +395,7 @@ def accept_args_markers(plugin: _TCallable) -> _TCallable:
399
395
  return plugin
400
396
 
401
397
 
402
- def accept_lazy_markers(plugin: _TCallable) -> _TCallable:
398
+ def accept_lazy_markers[T: _t.Callable](plugin: T) -> T:
403
399
  """
404
400
  A decorator to mark a Jinja plugin as capable of handling `Marker` values retrieved from lazy containers.
405
401
  Non-decorated plugins will trigger a `MarkerError` exception when attempting to retrieve a `Marker` from a lazy container.
@@ -62,6 +62,11 @@ class _AnsibleCollectionConfig(type):
62
62
  cls._require_finder()
63
63
  return [_to_text(p) for p in cls._collection_finder._n_collection_paths]
64
64
 
65
+ @property
66
+ def _internal_collections(cls):
67
+ cls._require_finder()
68
+ return cls._collection_finder._internal_collections
69
+
65
70
  @property
66
71
  def default_collection(cls):
67
72
  return cls._default_collection
@@ -26,20 +26,16 @@ from . import _to_bytes, _to_text
26
26
  from ._collection_config import AnsibleCollectionConfig
27
27
 
28
28
  try:
29
- try:
30
- # Available on Python >= 3.11
31
- # We ignore the import error that will trigger when running mypy with
32
- # older Python versions.
33
- from importlib.resources.abc import TraversableResources # type: ignore[import]
34
- except ImportError:
35
- # Used with Python 3.9 and 3.10 only
36
- # This member is still available as an alias up until Python 3.14 but
37
- # is deprecated as of Python 3.12.
38
- from importlib.abc import TraversableResources # deprecated: description='TraversableResources move' python_version='3.10'
29
+ # Available on Python >= 3.11
30
+ # We ignore the import error that will trigger when running mypy with
31
+ # older Python versions.
32
+ from importlib.resources.abc import TraversableResources # type: ignore[import]
39
33
  except ImportError:
40
- # Python < 3.9
41
- # deprecated: description='TraversableResources fallback' python_version='3.8'
42
- TraversableResources = object # type: ignore[assignment,misc]
34
+ # Used with Python 3.9 and 3.10 only
35
+ # This member is still available as an alias up until Python 3.14 but
36
+ # is deprecated as of Python 3.12.
37
+ # deprecated: description='TraversableResources move' python_version='3.10'
38
+ from importlib.abc import TraversableResources # type: ignore[assignment,no-redef]
43
39
 
44
40
  # NB: this supports import sanity test providing a different impl
45
41
  try:
@@ -186,7 +182,7 @@ class _AnsibleTraversableResources(TraversableResources):
186
182
 
187
183
 
188
184
  class _AnsibleCollectionFinder:
189
- def __init__(self, paths=None, scan_sys_paths=True):
185
+ def __init__(self, paths=None, scan_sys_paths=True, internal_collections=None):
190
186
  # TODO: accept metadata loader override
191
187
  self._ansible_pkg_path = _to_text(os.path.dirname(_to_bytes(sys.modules['ansible'].__file__)))
192
188
 
@@ -213,6 +209,7 @@ class _AnsibleCollectionFinder:
213
209
  if p not in good_paths and os.path.isdir(_to_bytes(os.path.join(p, 'ansible_collections'))):
214
210
  good_paths.append(p)
215
211
 
212
+ self._internal_collections = internal_collections
216
213
  self._n_configured_paths = good_paths
217
214
  self._n_cached_collection_paths = None
218
215
  self._n_cached_collection_qualified_paths = None
@@ -9,14 +9,14 @@ from __future__ import annotations
9
9
  from abc import ABCMeta
10
10
  from collections.abc import Container, Mapping, Sequence, Set
11
11
 
12
+ from ansible.module_utils._internal import _no_six
12
13
  from ansible.module_utils.common.collections import ImmutableDict
13
- from ansible.module_utils.six import add_metaclass, binary_type, text_type
14
14
  from ansible.utils.singleton import Singleton
15
15
 
16
16
 
17
17
  def _make_immutable(obj):
18
18
  """Recursively convert a container and objects inside of it into immutable data types"""
19
- if isinstance(obj, (text_type, binary_type)):
19
+ if isinstance(obj, (str, bytes)):
20
20
  # Strings first because they are also sequences
21
21
  return obj
22
22
  elif isinstance(obj, Mapping):
@@ -79,11 +79,14 @@ class CLIArgs(ImmutableDict):
79
79
  return cls(vars(options))
80
80
 
81
81
 
82
- @add_metaclass(_ABCSingleton)
83
- class GlobalCLIArgs(CLIArgs):
82
+ class GlobalCLIArgs(CLIArgs, metaclass=_ABCSingleton):
84
83
  """
85
84
  Globally hold a parsed copy of cli arguments.
86
85
 
87
86
  Only one of these exist per program as it is for global context
88
87
  """
89
88
  pass
89
+
90
+
91
+ def __getattr__(importable_name):
92
+ return _no_six.deprecate(importable_name, __name__, "binary_type", "text_type", "add_metaclass")
ansible/utils/display.py CHANGED
@@ -52,11 +52,10 @@ from ansible.constants import config
52
52
  from ansible.errors import AnsibleAssertionError, AnsiblePromptInterrupt, AnsiblePromptNoninteractive, AnsibleError
53
53
  from ansible._internal._errors import _error_utils, _error_factory
54
54
  from ansible._internal import _display_utils
55
- from ansible.module_utils._internal import _deprecator, _messages
55
+ from ansible.module_utils._internal import _deprecator, _messages, _no_six
56
56
  from ansible.module_utils.common.text.converters import to_bytes, to_text
57
57
  from ansible.module_utils.datatag import deprecator_from_collection_name
58
58
  from ansible._internal._datatag._tags import TrustedAsTemplate
59
- from ansible.module_utils.six import text_type
60
59
  from ansible.module_utils._internal import _traceback, _errors
61
60
  from ansible.utils.color import stringc
62
61
  from ansible.utils.multiprocessing import context as multiprocessing_context
@@ -66,8 +65,6 @@ if t.TYPE_CHECKING:
66
65
  # avoid circular import at runtime
67
66
  from ansible.executor.task_queue_manager import FinalQueue
68
67
 
69
- P = t.ParamSpec('P')
70
-
71
68
  _LIBC = ctypes.cdll.LoadLibrary(ctypes.util.find_library('c'))
72
69
  # Set argtypes, to avoid segfault if the wrong type is provided,
73
70
  # restype is assumed to be c_int
@@ -118,7 +115,7 @@ def get_text_width(text: str) -> int:
118
115
  character and using wcwidth individually, falling back to a value of 0
119
116
  for non-printable wide characters.
120
117
  """
121
- if not isinstance(text, text_type):
118
+ if not isinstance(text, str):
122
119
  raise TypeError('get_text_width requires text, not %s' % type(text))
123
120
 
124
121
  try:
@@ -398,7 +395,7 @@ class Display(metaclass=Singleton):
398
395
  self.b_cowsay = b_cow_path
399
396
 
400
397
  @staticmethod
401
- def _proxy(
398
+ def _proxy[**P](
402
399
  func: c.Callable[t.Concatenate[Display, P], None]
403
400
  ) -> c.Callable[..., None]:
404
401
  @wraps(func)
@@ -1144,3 +1141,7 @@ def _report_config_warnings(deprecator: _messages.PluginInfo) -> None:
1144
1141
  # emit any warnings or deprecations
1145
1142
  # in the event config fails before display is up, we'll lose warnings -- but that's OK, since everything is broken anyway
1146
1143
  _report_config_warnings(_deprecator.ANSIBLE_CORE_DEPRECATOR)
1144
+
1145
+
1146
+ def __getattr__(importable_name):
1147
+ return _no_six.deprecate(importable_name, __name__, "text_type")
ansible/utils/encrypt.py CHANGED
@@ -176,11 +176,6 @@ class PasslibHash(BaseHash):
176
176
  return to_text(result, errors='strict')
177
177
 
178
178
 
179
- def passlib_or_crypt(secret, algorithm, salt=None, salt_size=None, rounds=None, ident=None):
180
- display.deprecated("passlib_or_crypt API is deprecated in favor of do_encrypt", version='2.20')
181
- return do_encrypt(secret, algorithm, salt=salt, salt_size=salt_size, rounds=rounds, ident=ident)
182
-
183
-
184
179
  def do_encrypt(result, encrypt, salt_size=None, salt=None, ident=None, rounds=None):
185
180
  if PASSLIB_AVAILABLE:
186
181
  return PasslibHash(encrypt).hash(result, salt=salt, salt_size=salt_size, rounds=rounds, ident=ident)
ansible/utils/helpers.py CHANGED
@@ -17,7 +17,7 @@
17
17
 
18
18
  from __future__ import annotations
19
19
 
20
- from ansible.module_utils.six import string_types
20
+ from ansible.module_utils._internal import _no_six
21
21
 
22
22
 
23
23
  def pct_to_int(value, num_items, min_value=1):
@@ -25,7 +25,7 @@ def pct_to_int(value, num_items, min_value=1):
25
25
  Converts a given value to a percentage if specified as "x%",
26
26
  otherwise converts the given value to an integer.
27
27
  """
28
- if isinstance(value, string_types) and value.endswith('%'):
28
+ if isinstance(value, str) and value.endswith('%'):
29
29
  value_pct = int(value.replace("%", ""))
30
30
  return int((value_pct / 100.0) * num_items) or min_value
31
31
  else:
@@ -47,3 +47,7 @@ def deduplicate_list(original_list):
47
47
  """
48
48
  seen = set()
49
49
  return [x for x in original_list if x not in seen and not seen.add(x)]
50
+
51
+
52
+ def __getattr__(importable_name):
53
+ return _no_six.deprecate(importable_name, __name__, "string_types")
ansible/utils/jsonrpc.py CHANGED
@@ -7,9 +7,9 @@ import json
7
7
  import pickle
8
8
  import traceback
9
9
 
10
+ from ansible.module_utils._internal import _no_six
10
11
  from ansible.module_utils.common.text.converters import to_text
11
12
  from ansible.module_utils.connection import ConnectionError
12
- from ansible.module_utils.six import binary_type, text_type
13
13
  from ansible.utils.display import Display
14
14
 
15
15
  display = Display()
@@ -79,9 +79,9 @@ class JsonRpcServer(object):
79
79
 
80
80
  def response(self, result=None):
81
81
  response = self.header()
82
- if isinstance(result, binary_type):
82
+ if isinstance(result, bytes):
83
83
  result = to_text(result)
84
- if not isinstance(result, text_type):
84
+ if not isinstance(result, str):
85
85
  response["result_type"] = "pickle"
86
86
  result = to_text(pickle.dumps(result), errors='surrogateescape')
87
87
  response['result'] = result
@@ -110,3 +110,7 @@ class JsonRpcServer(object):
110
110
 
111
111
  def internal_error(self, data=None):
112
112
  return self.error(-32603, 'Internal error', data)
113
+
114
+
115
+ def __getattr__(importable_name):
116
+ return _no_six.deprecate(importable_name, __name__, "binary_type", "text_type")
@@ -11,13 +11,14 @@ import yaml
11
11
  from ansible import constants as C
12
12
  from ansible.release import __version__ as ansible_version
13
13
  from ansible.errors import AnsibleError, AnsibleParserError, AnsiblePluginNotFound
14
- from ansible.module_utils.six import string_types
14
+ from ansible.module_utils._internal import _no_six
15
15
  from ansible.module_utils.common.text.converters import to_native
16
16
  from ansible.parsing.plugin_docs import read_docstring
17
17
  from ansible.parsing.yaml.loader import AnsibleLoader
18
18
  from ansible.utils.display import Display
19
19
  from ansible._internal._datatag import _tags
20
20
 
21
+ _FRAGMENTABLE = ('DOCUMENTATION', 'RETURN')
21
22
  display = Display()
22
23
 
23
24
 
@@ -125,23 +126,30 @@ def remove_current_collection_from_versions_and_dates(fragment, collection_name,
125
126
  _process_versions_and_dates(fragment, is_module, return_docs, remove)
126
127
 
127
128
 
128
- def add_fragments(doc, filename, fragment_loader, is_module=False):
129
+ class AnsibleFragmentError(AnsibleError):
130
+ pass
131
+
132
+
133
+ def add_fragments(doc, filename, fragment_loader, is_module=False, section='DOCUMENTATION'):
134
+
135
+ if section not in _FRAGMENTABLE:
136
+ raise AnsibleError(f"Invalid fragment section ({section}) passed to render {filename}, it can only be one of {_FRAGMENTABLE!r}")
129
137
 
130
138
  fragments = doc.pop('extends_documentation_fragment', [])
131
139
 
132
- if isinstance(fragments, string_types):
140
+ if isinstance(fragments, str):
133
141
  fragments = fragments.split(',')
134
142
 
135
143
  unknown_fragments = []
136
144
 
137
- # doc_fragments are allowed to specify a fragment var other than DOCUMENTATION
145
+ # doc_fragments are allowed to specify a fragment var other than DOCUMENTATION or RETURN
138
146
  # with a . separator; this is complicated by collections-hosted doc_fragments that
139
147
  # use the same separator. Assume it's collection-hosted normally first, try to load
140
148
  # as-specified. If failure, assume the right-most component is a var, split it off,
141
149
  # and retry the load.
142
150
  for fragment_slug in fragments:
143
151
  fragment_name = fragment_slug.strip()
144
- fragment_var = 'DOCUMENTATION'
152
+ fragment_var = section
145
153
 
146
154
  fragment_class = fragment_loader.get(fragment_name)
147
155
  if fragment_class is None and '.' in fragment_slug:
@@ -157,7 +165,7 @@ def add_fragments(doc, filename, fragment_loader, is_module=False):
157
165
  # trust-tagged source propagates to loaded values; expressions and templates in config require trust
158
166
  fragment_yaml = _tags.TrustedAsTemplate().tag(getattr(fragment_class, fragment_var, None))
159
167
  if fragment_yaml is None:
160
- if fragment_var != 'DOCUMENTATION':
168
+ if fragment_var not in _FRAGMENTABLE:
161
169
  # if it's asking for something specific that's missing, that's an error
162
170
  unknown_fragments.append(fragment_slug)
163
171
  continue
@@ -168,44 +176,40 @@ def add_fragments(doc, filename, fragment_loader, is_module=False):
168
176
 
169
177
  real_fragment_name = getattr(fragment_class, 'ansible_name')
170
178
  real_collection_name = '.'.join(real_fragment_name.split('.')[0:2]) if '.' in real_fragment_name else ''
171
- add_collection_to_versions_and_dates(fragment, real_collection_name, is_module=is_module)
172
-
173
- if 'notes' in fragment:
174
- notes = fragment.pop('notes')
175
- if notes:
176
- if 'notes' not in doc:
177
- doc['notes'] = []
178
- doc['notes'].extend(notes)
179
-
180
- if 'seealso' in fragment:
181
- seealso = fragment.pop('seealso')
182
- if seealso:
183
- if 'seealso' not in doc:
184
- doc['seealso'] = []
185
- doc['seealso'].extend(seealso)
186
-
187
- if 'options' not in fragment and 'attributes' not in fragment:
188
- raise Exception("missing options or attributes in fragment (%s), possibly misformatted?: %s" % (fragment_name, filename))
189
-
190
- # ensure options themselves are directly merged
191
- for doc_key in ['options', 'attributes']:
192
- if doc_key in fragment:
193
- if doc_key in doc:
194
- try:
195
- merge_fragment(doc[doc_key], fragment.pop(doc_key))
196
- except Exception as e:
197
- raise AnsibleError("%s %s (%s) of unknown type: %s" % (to_native(e), doc_key, fragment_name, filename))
198
- else:
199
- doc[doc_key] = fragment.pop(doc_key)
179
+ add_collection_to_versions_and_dates(fragment, real_collection_name, is_module=is_module, return_docs=(section == 'RETURN'))
180
+
181
+ if section == 'DOCUMENTATION':
182
+ # notes, seealso, options and attributes entries are specificly merged, but only occur in documentation section
183
+ for doc_key in ['notes', 'seealso']:
184
+ if doc_key in fragment:
185
+ entries = fragment.pop(doc_key)
186
+ if entries:
187
+ if doc_key not in doc:
188
+ doc[doc_key] = []
189
+ doc[doc_key].extend(entries)
190
+
191
+ if 'options' not in fragment and 'attributes' not in fragment:
192
+ raise AnsibleFragmentError("missing options or attributes in fragment (%s), possibly misformatted?: %s" % (fragment_name, filename))
193
+
194
+ # ensure options themselves are directly merged
195
+ for doc_key in ['options', 'attributes']:
196
+ if doc_key in fragment:
197
+ if doc_key in doc:
198
+ try:
199
+ merge_fragment(doc[doc_key], fragment.pop(doc_key))
200
+ except Exception as e:
201
+ raise AnsibleFragmentError("%s %s (%s) of unknown type: %s" % (to_native(e), doc_key, fragment_name, filename))
202
+ else:
203
+ doc[doc_key] = fragment.pop(doc_key)
200
204
 
201
205
  # merge rest of the sections
202
206
  try:
203
207
  merge_fragment(doc, fragment)
204
208
  except Exception as e:
205
- raise AnsibleError("%s (%s) of unknown type: %s" % (to_native(e), fragment_name, filename))
209
+ raise AnsibleFragmentError("%s (%s) of unknown type: %s" % (to_native(e), fragment_name, filename))
206
210
 
207
211
  if unknown_fragments:
208
- raise AnsibleError('unknown doc_fragment(s) in file {0}: {1}'.format(filename, to_native(', '.join(unknown_fragments))))
212
+ raise AnsibleFragmentError('unknown doc_fragment(s) in file {0}: {1}'.format(filename, to_native(', '.join(unknown_fragments))))
209
213
 
210
214
 
211
215
  def get_docstring(filename, fragment_loader, verbose=False, ignore_errors=False, collection_name=None, is_module=None, plugin_type=None):
@@ -230,13 +234,16 @@ def get_docstring(filename, fragment_loader, verbose=False, ignore_errors=False,
230
234
  add_collection_to_versions_and_dates(data['doc'], collection_name, is_module=is_module)
231
235
 
232
236
  # add fragments to documentation
233
- add_fragments(data['doc'], filename, fragment_loader=fragment_loader, is_module=is_module)
237
+ add_fragments(data['doc'], filename, fragment_loader=fragment_loader, is_module=is_module, section='DOCUMENTATION')
234
238
 
235
239
  if data.get('returndocs', False):
236
240
  # add collection name to versions and dates
237
241
  if collection_name is not None:
238
242
  add_collection_to_versions_and_dates(data['returndocs'], collection_name, is_module=is_module, return_docs=True)
239
243
 
244
+ # add fragments to return
245
+ add_fragments(data['returndocs'], filename, fragment_loader=fragment_loader, is_module=is_module, section='RETURN')
246
+
240
247
  return data['doc'], data['plainexamples'], data['returndocs'], data['metadata']
241
248
 
242
249
 
@@ -352,3 +359,7 @@ def get_plugin_docs(plugin, plugin_type, loader, fragment_loader, verbose):
352
359
  docs[0]['plugin_name'] = context.resolved_fqcn
353
360
 
354
361
  return docs
362
+
363
+
364
+ def __getattr__(importable_name):
365
+ return _no_six.deprecate(importable_name, __name__, "string_types")