ansible-core 2.19.2rc1__py3-none-any.whl → 2.20.0b1__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.
- ansible/_internal/__init__.py +1 -4
- ansible/_internal/_ansiballz/_builder.py +1 -3
- ansible/_internal/_collection_proxy.py +7 -9
- ansible/_internal/_display_utils.py +145 -0
- ansible/_internal/_json/__init__.py +3 -4
- ansible/_internal/_templating/_engine.py +1 -1
- ansible/_internal/_templating/_jinja_plugins.py +1 -2
- ansible/_internal/_wrapt.py +105 -301
- ansible/cli/__init__.py +11 -10
- ansible/cli/adhoc.py +1 -2
- ansible/cli/arguments/option_helpers.py +1 -1
- ansible/cli/config.py +5 -6
- ansible/cli/doc.py +67 -67
- ansible/cli/galaxy.py +15 -24
- ansible/cli/inventory.py +0 -1
- ansible/cli/playbook.py +0 -1
- ansible/cli/pull.py +0 -1
- ansible/cli/scripts/ansible_connection_cli_stub.py +1 -1
- ansible/config/base.yml +1 -25
- ansible/config/manager.py +0 -2
- ansible/executor/play_iterator.py +42 -20
- ansible/executor/playbook_executor.py +0 -9
- ansible/executor/powershell/async_watchdog.ps1 +24 -4
- ansible/executor/task_executor.py +32 -22
- ansible/executor/task_queue_manager.py +1 -3
- ansible/galaxy/api.py +33 -80
- ansible/galaxy/collection/__init__.py +4 -17
- ansible/galaxy/dependency_resolution/dataclasses.py +0 -10
- ansible/galaxy/dependency_resolution/providers.py +1 -2
- ansible/galaxy/role.py +1 -33
- ansible/inventory/manager.py +2 -3
- ansible/keyword_desc.yml +0 -3
- ansible/module_utils/_internal/_datatag/__init__.py +2 -10
- ansible/module_utils/_internal/_no_six.py +86 -0
- ansible/module_utils/_text.py +28 -8
- ansible/module_utils/ansible_release.py +2 -2
- ansible/module_utils/basic.py +27 -24
- ansible/module_utils/common/_collections_compat.py +11 -2
- ansible/module_utils/common/collections.py +8 -3
- ansible/module_utils/common/dict_transformations.py +1 -2
- ansible/module_utils/common/network.py +4 -2
- ansible/module_utils/common/parameters.py +32 -41
- ansible/module_utils/common/text/converters.py +109 -23
- ansible/module_utils/common/text/formatters.py +6 -2
- ansible/module_utils/common/validation.py +11 -9
- ansible/module_utils/connection.py +8 -3
- ansible/module_utils/facts/hardware/linux.py +23 -7
- ansible/module_utils/facts/hardware/netbsd.py +1 -1
- ansible/module_utils/facts/hardware/sunos.py +2 -1
- ansible/module_utils/facts/packages.py +6 -2
- ansible/module_utils/facts/system/distribution.py +2 -1
- ansible/module_utils/facts/system/env.py +6 -3
- ansible/module_utils/facts/system/local.py +3 -1
- ansible/module_utils/parsing/convert_bool.py +6 -2
- ansible/module_utils/service.py +2 -3
- ansible/module_utils/six/__init__.py +11 -6
- ansible/module_utils/urls.py +6 -2
- ansible/module_utils/yumdnf.py +0 -5
- ansible/modules/apt.py +18 -13
- ansible/modules/apt_repository.py +1 -1
- ansible/modules/assemble.py +5 -9
- ansible/modules/blockinfile.py +39 -23
- ansible/modules/cron.py +26 -35
- ansible/modules/deb822_repository.py +83 -12
- ansible/modules/dnf.py +3 -7
- ansible/modules/dnf5.py +4 -6
- ansible/modules/expect.py +0 -3
- ansible/modules/find.py +1 -2
- ansible/modules/get_url.py +1 -1
- ansible/modules/git.py +4 -5
- ansible/modules/include_vars.py +1 -1
- ansible/modules/lineinfile.py +71 -63
- ansible/modules/package_facts.py +1 -1
- ansible/modules/pip.py +8 -2
- ansible/modules/replace.py +6 -6
- ansible/modules/service.py +3 -4
- ansible/modules/stat.py +20 -0
- ansible/modules/uri.py +9 -10
- ansible/modules/user.py +1 -2
- ansible/modules/wait_for.py +2 -2
- ansible/modules/wait_for_connection.py +2 -1
- ansible/modules/yum_repository.py +1 -16
- ansible/parsing/dataloader.py +24 -31
- ansible/parsing/mod_args.py +3 -0
- ansible/parsing/vault/__init__.py +1 -2
- ansible/playbook/base.py +8 -56
- ansible/playbook/block.py +0 -60
- ansible/playbook/collectionsearch.py +1 -2
- ansible/playbook/handler.py +1 -7
- ansible/playbook/helpers.py +0 -7
- ansible/playbook/included_file.py +1 -1
- ansible/playbook/play.py +103 -37
- ansible/playbook/play_context.py +4 -0
- ansible/playbook/role/__init__.py +10 -65
- ansible/playbook/role/definition.py +3 -4
- ansible/playbook/role/include.py +2 -3
- ansible/playbook/role/metadata.py +1 -12
- ansible/playbook/role/requirement.py +1 -2
- ansible/playbook/role_include.py +1 -2
- ansible/playbook/taggable.py +16 -5
- ansible/playbook/task.py +51 -55
- ansible/plugins/action/__init__.py +20 -19
- ansible/plugins/action/add_host.py +1 -2
- ansible/plugins/action/fetch.py +2 -4
- ansible/plugins/action/group_by.py +1 -2
- ansible/plugins/action/include_vars.py +20 -22
- ansible/plugins/action/script.py +1 -3
- ansible/plugins/action/template.py +1 -2
- ansible/plugins/action/uri.py +4 -2
- ansible/plugins/cache/__init__.py +1 -0
- ansible/plugins/callback/__init__.py +13 -6
- ansible/plugins/connection/__init__.py +3 -7
- ansible/plugins/connection/local.py +2 -3
- ansible/plugins/connection/psrp.py +0 -2
- ansible/plugins/connection/ssh.py +2 -7
- ansible/plugins/connection/winrm.py +0 -2
- ansible/plugins/doc_fragments/result_format_callback.py +15 -0
- ansible/plugins/filter/core.py +4 -5
- ansible/plugins/filter/encryption.py +3 -27
- ansible/plugins/filter/mathstuff.py +1 -2
- ansible/plugins/filter/to_nice_yaml.yml +31 -3
- ansible/plugins/filter/to_yaml.yml +29 -12
- ansible/plugins/inventory/__init__.py +1 -2
- ansible/plugins/inventory/script.py +2 -1
- ansible/plugins/inventory/toml.py +3 -6
- ansible/plugins/inventory/yaml.py +1 -2
- ansible/plugins/list.py +10 -3
- ansible/plugins/loader.py +6 -6
- ansible/plugins/lookup/password.py +1 -2
- ansible/plugins/lookup/subelements.py +2 -3
- ansible/plugins/lookup/url.py +1 -1
- ansible/plugins/lookup/varnames.py +1 -2
- ansible/plugins/shell/__init__.py +9 -4
- ansible/plugins/shell/powershell.py +8 -24
- ansible/plugins/strategy/__init__.py +6 -3
- ansible/plugins/test/core.py +4 -1
- ansible/plugins/test/regex.yml +18 -6
- ansible/release.py +2 -2
- ansible/template/__init__.py +3 -7
- ansible/utils/collection_loader/_collection_config.py +5 -0
- ansible/utils/collection_loader/_collection_finder.py +11 -14
- ansible/utils/context_objects.py +7 -4
- ansible/utils/display.py +28 -167
- ansible/utils/encrypt.py +0 -5
- ansible/utils/helpers.py +6 -2
- ansible/utils/jsonrpc.py +7 -3
- ansible/utils/plugin_docs.py +49 -38
- ansible/utils/ssh_functions.py +0 -19
- ansible/utils/unsafe_proxy.py +7 -7
- ansible/vars/clean.py +2 -3
- ansible/vars/manager.py +27 -20
- ansible/vars/plugins.py +1 -31
- {ansible_core-2.19.2rc1.dist-info → ansible_core-2.20.0b1.dist-info}/METADATA +3 -3
- {ansible_core-2.19.2rc1.dist-info → ansible_core-2.20.0b1.dist-info}/RECORD +200 -200
- ansible_test/_data/completion/docker.txt +7 -7
- ansible_test/_data/completion/network.txt +0 -1
- ansible_test/_data/completion/remote.txt +4 -4
- ansible_test/_data/requirements/ansible-test.txt +1 -1
- ansible_test/_data/requirements/sanity.changelog.txt +1 -1
- ansible_test/_data/requirements/sanity.pep8.txt +1 -1
- ansible_test/_data/requirements/sanity.pylint.txt +4 -4
- ansible_test/_internal/cache.py +2 -5
- ansible_test/_internal/cli/compat.py +1 -1
- ansible_test/_internal/commands/coverage/combine.py +1 -3
- ansible_test/_internal/commands/integration/__init__.py +3 -7
- ansible_test/_internal/commands/integration/cloud/httptester.py +1 -1
- ansible_test/_internal/commands/integration/coverage.py +1 -3
- ansible_test/_internal/commands/integration/filters.py +5 -10
- ansible_test/_internal/commands/sanity/validate_modules.py +1 -5
- ansible_test/_internal/commands/units/__init__.py +1 -13
- ansible_test/_internal/completion.py +2 -5
- ansible_test/_internal/config.py +2 -7
- ansible_test/_internal/coverage_util.py +1 -1
- ansible_test/_internal/delegation.py +2 -0
- ansible_test/_internal/docker_util.py +1 -1
- ansible_test/_internal/host_profiles.py +6 -11
- ansible_test/_internal/provider/__init__.py +2 -5
- ansible_test/_internal/provisioning.py +2 -5
- ansible_test/_internal/pypi_proxy.py +1 -1
- ansible_test/_internal/target.py +2 -6
- ansible_test/_internal/thread.py +1 -4
- ansible_test/_internal/util.py +9 -14
- ansible_test/_util/controller/sanity/code-smell/runtime-metadata.py +14 -19
- ansible_test/_util/controller/sanity/pylint/plugins/unwanted.py +30 -27
- ansible_test/_util/controller/sanity/validate-modules/validate_modules/main.py +31 -18
- ansible_test/_util/controller/sanity/validate-modules/validate_modules/module_args.py +1 -2
- ansible_test/_util/controller/sanity/validate-modules/validate_modules/schema.py +59 -71
- ansible_test/_util/controller/sanity/validate-modules/validate_modules/utils.py +1 -2
- ansible_test/_util/target/cli/ansible_test_cli_stub.py +4 -2
- ansible_test/_util/target/common/constants.py +2 -2
- ansible_test/_util/target/setup/bootstrap.sh +0 -6
- ansible/utils/py3compat.py +0 -27
- ansible_test/_data/pytest/config/legacy.ini +0 -4
- {ansible_core-2.19.2rc1.dist-info → ansible_core-2.20.0b1.dist-info}/WHEEL +0 -0
- {ansible_core-2.19.2rc1.dist-info → ansible_core-2.20.0b1.dist-info}/entry_points.txt +0 -0
- {ansible_core-2.19.2rc1.dist-info → ansible_core-2.20.0b1.dist-info}/licenses/COPYING +0 -0
- {ansible_core-2.19.2rc1.dist-info → ansible_core-2.20.0b1.dist-info}/licenses/licenses/Apache-License.txt +0 -0
- {ansible_core-2.19.2rc1.dist-info → ansible_core-2.20.0b1.dist-info}/licenses/licenses/BSD-3-Clause.txt +0 -0
- {ansible_core-2.19.2rc1.dist-info → ansible_core-2.20.0b1.dist-info}/licenses/licenses/MIT-license.txt +0 -0
- {ansible_core-2.19.2rc1.dist-info → ansible_core-2.20.0b1.dist-info}/licenses/licenses/PSF-license.txt +0 -0
- {ansible_core-2.19.2rc1.dist-info → ansible_core-2.20.0b1.dist-info}/licenses/licenses/simplified_bsd.txt +0 -0
- {ansible_core-2.19.2rc1.dist-info → ansible_core-2.20.0b1.dist-info}/top_level.txt +0 -0
|
@@ -29,7 +29,6 @@ from ansible.module_utils.common.arg_spec import ArgumentSpecValidator
|
|
|
29
29
|
from ansible.module_utils.errors import UnsupportedError
|
|
30
30
|
from ansible.module_utils.json_utils import _filter_non_json_lines
|
|
31
31
|
from ansible.module_utils.common.json import Direction, get_module_encoder, get_module_decoder
|
|
32
|
-
from ansible.module_utils.six import binary_type, string_types, text_type
|
|
33
32
|
from ansible.module_utils.common.text.converters import to_bytes, to_native, to_text
|
|
34
33
|
from ansible.release import __version__
|
|
35
34
|
from ansible.utils.collection_loader import resource_from_fqcr
|
|
@@ -52,7 +51,7 @@ if t.TYPE_CHECKING:
|
|
|
52
51
|
|
|
53
52
|
|
|
54
53
|
def _validate_utf8_json(d):
|
|
55
|
-
if isinstance(d,
|
|
54
|
+
if isinstance(d, str):
|
|
56
55
|
# Purposefully not using to_bytes here for performance reasons
|
|
57
56
|
d.encode(encoding='utf-8', errors='strict')
|
|
58
57
|
elif isinstance(d, dict):
|
|
@@ -288,14 +287,6 @@ class ActionBase(ABC, _AnsiblePluginInfoMixin):
|
|
|
288
287
|
elif leaf_module_name == 'async_status' and collection_name in rewrite_collection_names:
|
|
289
288
|
module_name = '%s.%s' % (win_collection, leaf_module_name)
|
|
290
289
|
|
|
291
|
-
# TODO: move this tweak down to the modules, not extensible here
|
|
292
|
-
# Remove extra quotes surrounding path parameters before sending to module.
|
|
293
|
-
if leaf_module_name in ['win_stat', 'win_file', 'win_copy', 'slurp'] and module_args and \
|
|
294
|
-
hasattr(self._connection._shell, '_unquote'):
|
|
295
|
-
for key in ('src', 'dest', 'path'):
|
|
296
|
-
if key in module_args:
|
|
297
|
-
module_args[key] = self._connection._shell._unquote(module_args[key])
|
|
298
|
-
|
|
299
290
|
result = self._shared_loader_obj.module_loader.find_plugin_with_context(module_name, mod_type, collection_list=self._task.collections)
|
|
300
291
|
|
|
301
292
|
if not result.resolved:
|
|
@@ -680,8 +671,18 @@ class ActionBase(ABC, _AnsiblePluginInfoMixin):
|
|
|
680
671
|
become_user,
|
|
681
672
|
setfacl_mode)
|
|
682
673
|
|
|
683
|
-
|
|
684
|
-
|
|
674
|
+
match res.get('rc'):
|
|
675
|
+
case 0:
|
|
676
|
+
return remote_paths
|
|
677
|
+
case 2:
|
|
678
|
+
# invalid syntax (for example, missing user, missing colon)
|
|
679
|
+
self._display.debug(f"setfacl command failed with an invalid syntax. Trying chmod instead. Err: {res!r}")
|
|
680
|
+
case 127:
|
|
681
|
+
# setfacl binary does not exists or we don't have permission to use it.
|
|
682
|
+
self._display.debug(f"setfacl binary does not exist or does not have permission to use it. Trying chmod instead. Err: {res!r}")
|
|
683
|
+
case _:
|
|
684
|
+
# generic debug message
|
|
685
|
+
self._display.debug(f'Failed to set facl {setfacl_mode}, got:{res!r}')
|
|
685
686
|
|
|
686
687
|
# Step 3b: Set execute if we need to. We do this before anything else
|
|
687
688
|
# because some of the methods below might work but not let us set
|
|
@@ -874,7 +875,7 @@ class ActionBase(ABC, _AnsiblePluginInfoMixin):
|
|
|
874
875
|
# happens sometimes when it is a dir and not on bsd
|
|
875
876
|
if 'checksum' not in mystat['stat']:
|
|
876
877
|
mystat['stat']['checksum'] = ''
|
|
877
|
-
elif not isinstance(mystat['stat']['checksum'],
|
|
878
|
+
elif not isinstance(mystat['stat']['checksum'], str):
|
|
878
879
|
raise AnsibleError("Invalid checksum returned by stat: expected a string type but got %s" % type(mystat['stat']['checksum']))
|
|
879
880
|
|
|
880
881
|
return mystat['stat']
|
|
@@ -1084,7 +1085,7 @@ class ActionBase(ABC, _AnsiblePluginInfoMixin):
|
|
|
1084
1085
|
# the remote system, which can be read and parsed by the module
|
|
1085
1086
|
args_data = ""
|
|
1086
1087
|
for k, v in module_args.items():
|
|
1087
|
-
args_data += '%s=%s ' % (k, shlex.quote(
|
|
1088
|
+
args_data += '%s=%s ' % (k, shlex.quote(str(v)))
|
|
1088
1089
|
self._transfer_data(args_file_path, args_data)
|
|
1089
1090
|
elif module_style in ('non_native_want_json', 'binary'):
|
|
1090
1091
|
profile_encoder = get_module_encoder(module_bits.serialization_profile, Direction.CONTROLLER_TO_MODULE)
|
|
@@ -1169,7 +1170,7 @@ class ActionBase(ABC, _AnsiblePluginInfoMixin):
|
|
|
1169
1170
|
self._cleanup_remote_tmp = False
|
|
1170
1171
|
|
|
1171
1172
|
# NOTE: dnf returns results .. but that made it 'compatible' with squashing, so we allow mappings, for now
|
|
1172
|
-
if 'results' in data and (not isinstance(data['results'], Sequence) or isinstance(data['results'],
|
|
1173
|
+
if 'results' in data and (not isinstance(data['results'], Sequence) or isinstance(data['results'], str)):
|
|
1173
1174
|
data['ansible_module_results'] = data['results']
|
|
1174
1175
|
del data['results']
|
|
1175
1176
|
display.warning("Found internal 'results' key in module return, renamed to 'ansible_module_results'.")
|
|
@@ -1322,16 +1323,16 @@ class ActionBase(ABC, _AnsiblePluginInfoMixin):
|
|
|
1322
1323
|
|
|
1323
1324
|
# stdout and stderr may be either a file-like or a bytes object.
|
|
1324
1325
|
# Convert either one to a text type
|
|
1325
|
-
if isinstance(stdout,
|
|
1326
|
+
if isinstance(stdout, bytes):
|
|
1326
1327
|
out = to_text(stdout, errors=encoding_errors)
|
|
1327
|
-
elif not isinstance(stdout,
|
|
1328
|
+
elif not isinstance(stdout, str):
|
|
1328
1329
|
out = to_text(b''.join(stdout.readlines()), errors=encoding_errors)
|
|
1329
1330
|
else:
|
|
1330
1331
|
out = stdout
|
|
1331
1332
|
|
|
1332
|
-
if isinstance(stderr,
|
|
1333
|
+
if isinstance(stderr, bytes):
|
|
1333
1334
|
err = to_text(stderr, errors=encoding_errors)
|
|
1334
|
-
elif not isinstance(stderr,
|
|
1335
|
+
elif not isinstance(stderr, str):
|
|
1335
1336
|
err = to_text(b''.join(stderr.readlines()), errors=encoding_errors)
|
|
1336
1337
|
else:
|
|
1337
1338
|
err = stderr
|
|
@@ -21,7 +21,6 @@ from __future__ import annotations
|
|
|
21
21
|
from collections.abc import Mapping
|
|
22
22
|
|
|
23
23
|
from ansible.errors import AnsibleActionFail
|
|
24
|
-
from ansible.module_utils.six import string_types
|
|
25
24
|
from ansible.plugins.action import ActionBase
|
|
26
25
|
from ansible.parsing.utils.addresses import parse_address
|
|
27
26
|
from ansible.utils.display import Display
|
|
@@ -74,7 +73,7 @@ class ActionModule(ActionBase):
|
|
|
74
73
|
if groups:
|
|
75
74
|
if isinstance(groups, list):
|
|
76
75
|
group_list = groups
|
|
77
|
-
elif isinstance(groups,
|
|
76
|
+
elif isinstance(groups, str):
|
|
78
77
|
group_list = groups.split(",")
|
|
79
78
|
else:
|
|
80
79
|
raise AnsibleActionFail("Groups must be specified as a list.", obj=groups)
|
ansible/plugins/action/fetch.py
CHANGED
|
@@ -20,7 +20,6 @@ import os
|
|
|
20
20
|
import base64
|
|
21
21
|
from ansible.errors import AnsibleConnectionFailure, AnsibleError, AnsibleActionFail, AnsibleActionSkip
|
|
22
22
|
from ansible.module_utils.common.text.converters import to_bytes, to_text
|
|
23
|
-
from ansible.module_utils.six import string_types
|
|
24
23
|
from ansible.module_utils.parsing.convert_bool import boolean
|
|
25
24
|
from ansible.plugins.action import ActionBase
|
|
26
25
|
from ansible.utils.display import Display
|
|
@@ -52,10 +51,10 @@ class ActionModule(ActionBase):
|
|
|
52
51
|
|
|
53
52
|
msg = ''
|
|
54
53
|
# FIXME: validate source and dest are strings; use basic.py and module specs
|
|
55
|
-
if not isinstance(source,
|
|
54
|
+
if not isinstance(source, str):
|
|
56
55
|
msg = "Invalid type supplied for source option, it must be a string"
|
|
57
56
|
|
|
58
|
-
if not isinstance(dest,
|
|
57
|
+
if not isinstance(dest, str):
|
|
59
58
|
msg = "Invalid type supplied for dest option, it must be a string"
|
|
60
59
|
|
|
61
60
|
if source is None or dest is None:
|
|
@@ -131,7 +130,6 @@ class ActionModule(ActionBase):
|
|
|
131
130
|
|
|
132
131
|
# calculate the destination name
|
|
133
132
|
if os.path.sep not in self._connection._shell.join_path('a', ''):
|
|
134
|
-
source = self._connection._shell._unquote(source)
|
|
135
133
|
source_local = source.replace('\\', '/')
|
|
136
134
|
else:
|
|
137
135
|
source_local = source
|
|
@@ -17,7 +17,6 @@
|
|
|
17
17
|
from __future__ import annotations
|
|
18
18
|
|
|
19
19
|
from ansible.plugins.action import ActionBase
|
|
20
|
-
from ansible.module_utils.six import string_types
|
|
21
20
|
|
|
22
21
|
|
|
23
22
|
class ActionModule(ActionBase):
|
|
@@ -42,7 +41,7 @@ class ActionModule(ActionBase):
|
|
|
42
41
|
|
|
43
42
|
group_name = self._task.args.get('key')
|
|
44
43
|
parent_groups = self._task.args.get('parents', ['all'])
|
|
45
|
-
if isinstance(parent_groups,
|
|
44
|
+
if isinstance(parent_groups, str):
|
|
46
45
|
parent_groups = [parent_groups]
|
|
47
46
|
|
|
48
47
|
result['changed'] = False
|
|
@@ -10,7 +10,6 @@ import pathlib
|
|
|
10
10
|
import ansible.constants as C
|
|
11
11
|
from ansible.errors import AnsibleError
|
|
12
12
|
from ansible._internal._datatag._tags import SourceWasEncrypted
|
|
13
|
-
from ansible.module_utils.six import string_types
|
|
14
13
|
from ansible.module_utils.common.text.converters import to_native
|
|
15
14
|
from ansible.plugins.action import ActionBase
|
|
16
15
|
from ansible.utils.vars import combine_vars
|
|
@@ -38,14 +37,17 @@ class ActionModule(ActionBase):
|
|
|
38
37
|
if not self.ignore_files:
|
|
39
38
|
self.ignore_files = list()
|
|
40
39
|
|
|
41
|
-
if isinstance(self.ignore_files,
|
|
40
|
+
if isinstance(self.ignore_files, str):
|
|
41
|
+
self._display.deprecated(
|
|
42
|
+
msg="Specifying 'ignore_files' as a string is deprecated.",
|
|
43
|
+
version="2.24",
|
|
44
|
+
help_text="Use a list of strings instead.",
|
|
45
|
+
obj=self.ignore_files,
|
|
46
|
+
)
|
|
42
47
|
self.ignore_files = self.ignore_files.split()
|
|
43
48
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
'failed': True,
|
|
47
|
-
'message': '{0} must be a list'.format(self.ignore_files)
|
|
48
|
-
}
|
|
49
|
+
if not isinstance(self.ignore_files, list):
|
|
50
|
+
raise AnsibleError("The 'ignore_files' option must be a list.", obj=self.ignore_files)
|
|
49
51
|
|
|
50
52
|
def _set_args(self):
|
|
51
53
|
""" Set instance variables based on the arguments that were passed """
|
|
@@ -65,11 +67,8 @@ class ActionModule(ActionBase):
|
|
|
65
67
|
self.ignore_files = self._task.args.get('ignore_files', None)
|
|
66
68
|
self.valid_extensions = self._task.args.get('extensions', self.VALID_FILE_EXTENSIONS)
|
|
67
69
|
|
|
68
|
-
# convert/validate extensions list
|
|
69
|
-
if isinstance(self.valid_extensions, string_types):
|
|
70
|
-
self.valid_extensions = list(self.valid_extensions)
|
|
71
70
|
if not isinstance(self.valid_extensions, list):
|
|
72
|
-
raise AnsibleError('
|
|
71
|
+
raise AnsibleError("The 'extensions' option must be a list.", obj=self.valid_extensions)
|
|
73
72
|
|
|
74
73
|
def run(self, tmp=None, task_vars=None):
|
|
75
74
|
""" Load yml files recursively from a directory.
|
|
@@ -93,10 +92,10 @@ class ActionModule(ActionBase):
|
|
|
93
92
|
elif arg in self.VALID_ALL:
|
|
94
93
|
pass
|
|
95
94
|
else:
|
|
96
|
-
raise AnsibleError('{
|
|
95
|
+
raise AnsibleError(f'{arg} is not a valid option in include_vars', obj=arg)
|
|
97
96
|
|
|
98
97
|
if dirs and files:
|
|
99
|
-
raise AnsibleError("You are mixing file only and dir only arguments, these are incompatible")
|
|
98
|
+
raise AnsibleError("You are mixing file only and dir only arguments, these are incompatible", obj=self._task.args)
|
|
100
99
|
|
|
101
100
|
# set internal vars from args
|
|
102
101
|
self._set_args()
|
|
@@ -108,13 +107,13 @@ class ActionModule(ActionBase):
|
|
|
108
107
|
self._set_root_dir()
|
|
109
108
|
if not path.exists(self.source_dir):
|
|
110
109
|
failed = True
|
|
111
|
-
err_msg =
|
|
110
|
+
err_msg = f"{self.source_dir} directory does not exist"
|
|
112
111
|
elif not path.isdir(self.source_dir):
|
|
113
112
|
failed = True
|
|
114
|
-
err_msg =
|
|
113
|
+
err_msg = f"{self.source_dir} is not a directory"
|
|
115
114
|
else:
|
|
116
115
|
for root_dir, filenames in self._traverse_dir_depth():
|
|
117
|
-
failed, err_msg, updated_results =
|
|
116
|
+
failed, err_msg, updated_results = self._load_files_in_dir(root_dir, filenames)
|
|
118
117
|
if failed:
|
|
119
118
|
break
|
|
120
119
|
results.update(updated_results)
|
|
@@ -175,7 +174,7 @@ class ActionModule(ActionBase):
|
|
|
175
174
|
self.source_dir = path.join(current_dir, self.source_dir)
|
|
176
175
|
|
|
177
176
|
def _log_walk(self, error):
|
|
178
|
-
self._display.vvv(
|
|
177
|
+
self._display.vvv(f"Issue with walking through {error.filename}: {error}")
|
|
179
178
|
|
|
180
179
|
def _traverse_dir_depth(self):
|
|
181
180
|
""" Recursively iterate over a directory and sort the files in
|
|
@@ -204,9 +203,8 @@ class ActionModule(ActionBase):
|
|
|
204
203
|
try:
|
|
205
204
|
if re.search(r'{0}$'.format(file_type), filename):
|
|
206
205
|
return True
|
|
207
|
-
except Exception:
|
|
208
|
-
|
|
209
|
-
raise AnsibleError(err_msg)
|
|
206
|
+
except Exception as ex:
|
|
207
|
+
raise AnsibleError(f'Invalid regular expression: {file_type!r}', obj=file_type) from ex
|
|
210
208
|
return False
|
|
211
209
|
|
|
212
210
|
def _is_valid_file_ext(self, source_file):
|
|
@@ -232,7 +230,7 @@ class ActionModule(ActionBase):
|
|
|
232
230
|
err_msg = ''
|
|
233
231
|
if validate_extensions and not self._is_valid_file_ext(filename):
|
|
234
232
|
failed = True
|
|
235
|
-
err_msg =
|
|
233
|
+
err_msg = f"{filename!r} does not have a valid extension: {', '.join(self.valid_extensions)}"
|
|
236
234
|
else:
|
|
237
235
|
data = self._loader.load_from_file(filename, cache='none', trusted_as_template=True)
|
|
238
236
|
|
|
@@ -243,7 +241,7 @@ class ActionModule(ActionBase):
|
|
|
243
241
|
|
|
244
242
|
if not isinstance(data, dict):
|
|
245
243
|
failed = True
|
|
246
|
-
err_msg =
|
|
244
|
+
err_msg = f"{filename!r} must be stored as a dictionary/hash"
|
|
247
245
|
else:
|
|
248
246
|
self.included_files.append(filename)
|
|
249
247
|
results.update(data)
|
ansible/plugins/action/script.py
CHANGED
|
@@ -139,8 +139,6 @@ class ActionModule(ActionBase):
|
|
|
139
139
|
else:
|
|
140
140
|
script_cmd = ' '.join([env_string, target_command])
|
|
141
141
|
|
|
142
|
-
script_cmd = self._connection._shell.wrap_for_exec(script_cmd)
|
|
143
|
-
|
|
144
142
|
exec_data = None
|
|
145
143
|
# PowerShell runs the script in a special wrapper to enable things
|
|
146
144
|
# like become and environment args
|
|
@@ -149,7 +147,7 @@ class ActionModule(ActionBase):
|
|
|
149
147
|
pc = self._task
|
|
150
148
|
exec_data = ps_manifest._create_powershell_wrapper(
|
|
151
149
|
name=f"ansible.builtin.script.{pathlib.Path(source).stem}",
|
|
152
|
-
module_data=to_bytes(script_cmd),
|
|
150
|
+
module_data=to_bytes(f"& {script_cmd}; exit $LASTEXITCODE"),
|
|
153
151
|
module_path=source,
|
|
154
152
|
module_args={},
|
|
155
153
|
environment=env_dict,
|
|
@@ -23,7 +23,6 @@ from ansible.config.manager import ensure_type
|
|
|
23
23
|
from ansible.errors import AnsibleError, AnsibleActionFail
|
|
24
24
|
from ansible.module_utils.common.text.converters import to_bytes, to_text, to_native
|
|
25
25
|
from ansible.module_utils.parsing.convert_bool import boolean
|
|
26
|
-
from ansible.module_utils.six import string_types
|
|
27
26
|
from ansible.plugins.action import ActionBase
|
|
28
27
|
from ansible.template import trust_as_template
|
|
29
28
|
from ansible._internal._templating import _template_vars
|
|
@@ -49,7 +48,7 @@ class ActionModule(ActionBase):
|
|
|
49
48
|
'block_end_string', 'comment_start_string', 'comment_end_string'):
|
|
50
49
|
if s_type in self._task.args:
|
|
51
50
|
value = ensure_type(self._task.args[s_type], 'string')
|
|
52
|
-
if value is not None and not isinstance(value,
|
|
51
|
+
if value is not None and not isinstance(value, str):
|
|
53
52
|
raise AnsibleActionFail("%s is expected to be a string, but got %s instead" % (s_type, type(value)))
|
|
54
53
|
self._task.args[s_type] = value
|
|
55
54
|
|
ansible/plugins/action/uri.py
CHANGED
|
@@ -7,6 +7,7 @@ from __future__ import annotations
|
|
|
7
7
|
|
|
8
8
|
import collections.abc as _c
|
|
9
9
|
import os
|
|
10
|
+
from copy import deepcopy
|
|
10
11
|
|
|
11
12
|
from ansible.errors import AnsibleActionFail
|
|
12
13
|
from ansible.module_utils.parsing.convert_bool import boolean
|
|
@@ -53,7 +54,8 @@ class ActionModule(ActionBase):
|
|
|
53
54
|
raise AnsibleActionFail(
|
|
54
55
|
'body must be mapping, cannot be type %s' % body.__class__.__name__
|
|
55
56
|
)
|
|
56
|
-
|
|
57
|
+
new_body = deepcopy(body)
|
|
58
|
+
for field, value in new_body.items():
|
|
57
59
|
if not isinstance(value, _c.MutableMapping):
|
|
58
60
|
continue
|
|
59
61
|
content = value.get('content')
|
|
@@ -70,7 +72,7 @@ class ActionModule(ActionBase):
|
|
|
70
72
|
value['filename'] = tmp_src
|
|
71
73
|
self._transfer_file(filename, tmp_src)
|
|
72
74
|
self._fixup_perms2((self._connection._shell.tmpdir, tmp_src))
|
|
73
|
-
kwargs['body'] =
|
|
75
|
+
kwargs['body'] = new_body
|
|
74
76
|
|
|
75
77
|
new_module_args = self._task.args | kwargs
|
|
76
78
|
|
|
@@ -162,6 +162,7 @@ class BaseFileCacheModule(BaseCacheModule):
|
|
|
162
162
|
except OSError as ex:
|
|
163
163
|
display.error_as_warning(f"Error in {self.plugin_name!r} cache plugin while trying to write to {tmpfile_path!r}.", exception=ex)
|
|
164
164
|
try:
|
|
165
|
+
os.close(tmpfile_handle) # os.rename fails if handle is still open in WSL
|
|
165
166
|
os.rename(tmpfile_path, cachefile)
|
|
166
167
|
os.chmod(cachefile, mode=S_IRWU_RG_RO)
|
|
167
168
|
except OSError as ex:
|
|
@@ -60,9 +60,6 @@ _YAML_BREAK_CHARS = '\n\x85\u2028\u2029' # NL, NEL, LS, PS
|
|
|
60
60
|
_SPACE_BREAK_RE = re.compile(fr' +([{_YAML_BREAK_CHARS}])')
|
|
61
61
|
|
|
62
62
|
|
|
63
|
-
_T_callable = t.TypeVar("_T_callable", bound=t.Callable)
|
|
64
|
-
|
|
65
|
-
|
|
66
63
|
class _AnsibleCallbackDumper(_dumper.AnsibleDumper):
|
|
67
64
|
def __init__(self, *args, lossy: bool = False, **kwargs):
|
|
68
65
|
super().__init__(*args, **kwargs)
|
|
@@ -293,7 +290,11 @@ class CallbackBase(AnsiblePlugin):
|
|
|
293
290
|
)
|
|
294
291
|
|
|
295
292
|
if not indent and any(indent_conditions):
|
|
296
|
-
|
|
293
|
+
try:
|
|
294
|
+
indent = self.get_option('result_indentation')
|
|
295
|
+
except KeyError:
|
|
296
|
+
# Callback does not declare result_indentation nor extend result_format_callback
|
|
297
|
+
indent = 4
|
|
297
298
|
if pretty_results is False:
|
|
298
299
|
# pretty_results=False overrides any specified indentation
|
|
299
300
|
indent = None
|
|
@@ -394,8 +395,14 @@ class CallbackBase(AnsiblePlugin):
|
|
|
394
395
|
# Callback does not declare pretty_results nor extend result_format_callback
|
|
395
396
|
pretty_results = None
|
|
396
397
|
|
|
398
|
+
try:
|
|
399
|
+
indent = self.get_option('result_indentation')
|
|
400
|
+
except KeyError:
|
|
401
|
+
# Callback does not declare result_indentation nor extend result_format_callback
|
|
402
|
+
indent = 4
|
|
403
|
+
|
|
397
404
|
if result_format == 'json':
|
|
398
|
-
return json.dumps(diff, sort_keys=True, indent=
|
|
405
|
+
return json.dumps(diff, sort_keys=True, indent=indent, separators=(u',', u': ')) + u'\n'
|
|
399
406
|
|
|
400
407
|
if result_format == 'yaml':
|
|
401
408
|
# None is a sentinel in this case that indicates default behavior
|
|
@@ -407,7 +414,7 @@ class CallbackBase(AnsiblePlugin):
|
|
|
407
414
|
allow_unicode=True,
|
|
408
415
|
Dumper=functools.partial(_AnsibleCallbackDumper, lossy=lossy),
|
|
409
416
|
default_flow_style=False,
|
|
410
|
-
indent=
|
|
417
|
+
indent=indent,
|
|
411
418
|
# sort_keys=sort_keys # This requires PyYAML>=5.1
|
|
412
419
|
),
|
|
413
420
|
' '
|
|
@@ -6,7 +6,6 @@ from __future__ import annotations
|
|
|
6
6
|
|
|
7
7
|
import collections.abc as c
|
|
8
8
|
import fcntl
|
|
9
|
-
import io
|
|
10
9
|
import os
|
|
11
10
|
import shlex
|
|
12
11
|
import typing as t
|
|
@@ -16,7 +15,7 @@ from functools import wraps
|
|
|
16
15
|
|
|
17
16
|
from ansible import constants as C
|
|
18
17
|
from ansible.errors import AnsibleValueOmittedError
|
|
19
|
-
from ansible.module_utils.common.text.converters import
|
|
18
|
+
from ansible.module_utils.common.text.converters import to_text
|
|
20
19
|
from ansible.playbook.play_context import PlayContext
|
|
21
20
|
from ansible.plugins import AnsiblePlugin
|
|
22
21
|
from ansible.plugins.become import BecomeBase
|
|
@@ -32,9 +31,6 @@ __all__ = ['ConnectionBase', 'ensure_connect']
|
|
|
32
31
|
|
|
33
32
|
BUFSIZE = 65536
|
|
34
33
|
|
|
35
|
-
P = t.ParamSpec('P')
|
|
36
|
-
T = t.TypeVar('T')
|
|
37
|
-
|
|
38
34
|
|
|
39
35
|
class ConnectionKwargs(t.TypedDict):
|
|
40
36
|
task_uuid: str
|
|
@@ -42,7 +38,7 @@ class ConnectionKwargs(t.TypedDict):
|
|
|
42
38
|
shell: t.NotRequired[ShellBase]
|
|
43
39
|
|
|
44
40
|
|
|
45
|
-
def ensure_connect(
|
|
41
|
+
def ensure_connect[T, **P](
|
|
46
42
|
func: c.Callable[t.Concatenate[ConnectionBase, P], T],
|
|
47
43
|
) -> c.Callable[t.Concatenate[ConnectionBase, P], T]:
|
|
48
44
|
@wraps(func)
|
|
@@ -135,7 +131,7 @@ class ConnectionBase(AnsiblePlugin):
|
|
|
135
131
|
pass
|
|
136
132
|
|
|
137
133
|
@abstractmethod
|
|
138
|
-
def _connect(self: T) -> T:
|
|
134
|
+
def _connect[T](self: T) -> T:
|
|
139
135
|
"""Connect to the host we've been initialized with"""
|
|
140
136
|
|
|
141
137
|
@ensure_connect
|
|
@@ -47,7 +47,6 @@ import typing as t
|
|
|
47
47
|
|
|
48
48
|
import ansible.constants as C
|
|
49
49
|
from ansible.errors import AnsibleError, AnsibleFileNotFound, AnsibleConnectionFailure
|
|
50
|
-
from ansible.module_utils.six import text_type, binary_type
|
|
51
50
|
from ansible.module_utils.common.text.converters import to_bytes, to_native, to_text
|
|
52
51
|
from ansible.plugins.connection import ConnectionBase
|
|
53
52
|
from ansible.utils.display import Display
|
|
@@ -100,7 +99,7 @@ class Connection(ConnectionBase):
|
|
|
100
99
|
display.vvv(u"EXEC {0}".format(to_text(cmd)), host=self._play_context.remote_addr)
|
|
101
100
|
display.debug("opening command with Popen()")
|
|
102
101
|
|
|
103
|
-
if isinstance(cmd, (
|
|
102
|
+
if isinstance(cmd, (str, bytes)):
|
|
104
103
|
cmd = to_text(cmd)
|
|
105
104
|
else:
|
|
106
105
|
cmd = map(to_text, cmd)
|
|
@@ -119,7 +118,7 @@ class Connection(ConnectionBase):
|
|
|
119
118
|
|
|
120
119
|
p = subprocess.Popen(
|
|
121
120
|
cmd,
|
|
122
|
-
shell=isinstance(cmd, (
|
|
121
|
+
shell=isinstance(cmd, (str, bytes)),
|
|
123
122
|
executable=executable,
|
|
124
123
|
cwd=self.cwd,
|
|
125
124
|
stdin=stdin,
|
|
@@ -489,7 +489,6 @@ class Connection(ConnectionBase):
|
|
|
489
489
|
def put_file(self, in_path: str, out_path: str) -> None:
|
|
490
490
|
super(Connection, self).put_file(in_path, out_path)
|
|
491
491
|
|
|
492
|
-
out_path = self._shell._unquote(out_path)
|
|
493
492
|
display.vvv("PUT %s TO %s" % (in_path, out_path), host=self._psrp_host)
|
|
494
493
|
|
|
495
494
|
script, in_data = _bootstrap_powershell_script('psrp_put_file.ps1', {
|
|
@@ -549,7 +548,6 @@ class Connection(ConnectionBase):
|
|
|
549
548
|
display.vvv("FETCH %s TO %s" % (in_path, out_path),
|
|
550
549
|
host=self._psrp_host)
|
|
551
550
|
|
|
552
|
-
in_path = self._shell._unquote(in_path)
|
|
553
551
|
out_path = out_path.replace('\\', '/')
|
|
554
552
|
b_out_path = to_bytes(out_path, errors='surrogate_or_strict')
|
|
555
553
|
|
|
@@ -34,8 +34,6 @@ DOCUMENTATION = """
|
|
|
34
34
|
- name: inventory_hostname
|
|
35
35
|
- name: ansible_host
|
|
36
36
|
- name: ansible_ssh_host
|
|
37
|
-
- name: delegated_vars['ansible_host']
|
|
38
|
-
- name: delegated_vars['ansible_ssh_host']
|
|
39
37
|
host_key_checking:
|
|
40
38
|
description: Determines if SSH should reject or not a connection after checking host keys.
|
|
41
39
|
default: True
|
|
@@ -443,7 +441,6 @@ from ansible.errors import (
|
|
|
443
441
|
AnsibleError,
|
|
444
442
|
AnsibleFileNotFound,
|
|
445
443
|
)
|
|
446
|
-
from ansible.module_utils.six import text_type, binary_type
|
|
447
444
|
from ansible.module_utils.common.text.converters import to_bytes, to_native, to_text
|
|
448
445
|
from ansible.plugins.connection import ConnectionBase, BUFSIZE
|
|
449
446
|
from ansible.plugins.shell.powershell import _replace_stderr_clixml
|
|
@@ -461,8 +458,6 @@ else:
|
|
|
461
458
|
|
|
462
459
|
display = Display()
|
|
463
460
|
|
|
464
|
-
P = t.ParamSpec('P')
|
|
465
|
-
|
|
466
461
|
# error messages that indicate 255 return code is not from ssh itself.
|
|
467
462
|
b_NOT_SSH_ERRORS = (b'Traceback (most recent call last):', # Python-2.6 when there's an exception
|
|
468
463
|
# while invoking a script via -m
|
|
@@ -549,7 +544,7 @@ def _handle_error(
|
|
|
549
544
|
display.vvv(msg, host=host)
|
|
550
545
|
|
|
551
546
|
|
|
552
|
-
def _ssh_retry(
|
|
547
|
+
def _ssh_retry[**P](
|
|
553
548
|
func: c.Callable[t.Concatenate[Connection, P], tuple[int, bytes, bytes]],
|
|
554
549
|
) -> c.Callable[t.Concatenate[Connection, P], tuple[int, bytes, bytes]]:
|
|
555
550
|
"""
|
|
@@ -1126,7 +1121,7 @@ class Connection(ConnectionBase):
|
|
|
1126
1121
|
|
|
1127
1122
|
p = None
|
|
1128
1123
|
|
|
1129
|
-
if isinstance(cmd, (
|
|
1124
|
+
if isinstance(cmd, (str, bytes)):
|
|
1130
1125
|
cmd = to_bytes(cmd)
|
|
1131
1126
|
else:
|
|
1132
1127
|
cmd = list(map(to_bytes, cmd))
|
|
@@ -768,7 +768,6 @@ class Connection(ConnectionBase):
|
|
|
768
768
|
|
|
769
769
|
def put_file(self, in_path: str, out_path: str) -> None:
|
|
770
770
|
super(Connection, self).put_file(in_path, out_path)
|
|
771
|
-
out_path = self._shell._unquote(out_path)
|
|
772
771
|
display.vvv('PUT "%s" TO "%s"' % (in_path, out_path), host=self._winrm_host)
|
|
773
772
|
if not os.path.exists(to_bytes(in_path, errors='surrogate_or_strict')):
|
|
774
773
|
raise AnsibleFileNotFound('file or module does not exist: "%s"' % to_native(in_path))
|
|
@@ -806,7 +805,6 @@ class Connection(ConnectionBase):
|
|
|
806
805
|
|
|
807
806
|
def fetch_file(self, in_path: str, out_path: str) -> None:
|
|
808
807
|
super(Connection, self).fetch_file(in_path, out_path)
|
|
809
|
-
in_path = self._shell._unquote(in_path)
|
|
810
808
|
out_path = out_path.replace('\\', '/')
|
|
811
809
|
# consistent with other connection plugins, we assume the caller has created the target dir
|
|
812
810
|
display.vvv('FETCH "%s" TO "%s"' % (in_path, out_path), host=self._winrm_host)
|
|
@@ -26,6 +26,21 @@ class ModuleDocFragment(object):
|
|
|
26
26
|
- json
|
|
27
27
|
- yaml
|
|
28
28
|
version_added: '2.13'
|
|
29
|
+
result_indentation:
|
|
30
|
+
name: Indentation of the result
|
|
31
|
+
description:
|
|
32
|
+
- Allows to configure indentation for YAML and verbose/pretty JSON.
|
|
33
|
+
- Please note that for O(result_format=yaml), only values between 2 and 9 will be handled as expected by PyYAML.
|
|
34
|
+
If indentation is set to 1, or to 10 or larger, the first level of indentation will be used,
|
|
35
|
+
but all further indentations will be by 2 spaces.
|
|
36
|
+
type: int
|
|
37
|
+
default: 4
|
|
38
|
+
env:
|
|
39
|
+
- name: ANSIBLE_CALLBACK_RESULT_INDENTATION
|
|
40
|
+
ini:
|
|
41
|
+
- key: callback_result_indentation
|
|
42
|
+
section: defaults
|
|
43
|
+
version_added: '2.20'
|
|
29
44
|
pretty_results:
|
|
30
45
|
name: Configure output for readability
|
|
31
46
|
description:
|
ansible/plugins/filter/core.py
CHANGED
|
@@ -29,7 +29,6 @@ from ansible._internal._templating import _lazy_containers
|
|
|
29
29
|
from ansible.errors import AnsibleFilterError, AnsibleTypeError, AnsibleTemplatePluginError
|
|
30
30
|
from ansible.module_utils.datatag import native_type_name
|
|
31
31
|
from ansible.module_utils.common.json import get_encoder, get_decoder
|
|
32
|
-
from ansible.module_utils.six import string_types, integer_types, text_type
|
|
33
32
|
from ansible.module_utils.common.text.converters import to_bytes, to_native, to_text
|
|
34
33
|
from ansible.module_utils.common.collections import is_sequence
|
|
35
34
|
from ansible.parsing.yaml.dumper import AnsibleDumper
|
|
@@ -279,7 +278,7 @@ def rand(environment, end, start=None, step=None, seed=None):
|
|
|
279
278
|
r = SystemRandom()
|
|
280
279
|
else:
|
|
281
280
|
r = Random(seed)
|
|
282
|
-
if isinstance(end,
|
|
281
|
+
if isinstance(end, int):
|
|
283
282
|
if not start:
|
|
284
283
|
start = 0
|
|
285
284
|
if not step:
|
|
@@ -556,7 +555,7 @@ def subelements(obj, subelements, skip_missing=False):
|
|
|
556
555
|
|
|
557
556
|
if isinstance(subelements, list):
|
|
558
557
|
subelement_list = subelements[:]
|
|
559
|
-
elif isinstance(subelements,
|
|
558
|
+
elif isinstance(subelements, str):
|
|
560
559
|
subelement_list = subelements.split('.')
|
|
561
560
|
else:
|
|
562
561
|
raise AnsibleTypeError('subelements must be a list or a string')
|
|
@@ -618,7 +617,7 @@ def list_of_dict_key_value_elements_to_dict(mylist, key_name='key', value_name='
|
|
|
618
617
|
def path_join(paths):
|
|
619
618
|
""" takes a sequence or a string, and return a concatenation
|
|
620
619
|
of the different members """
|
|
621
|
-
if isinstance(paths,
|
|
620
|
+
if isinstance(paths, str):
|
|
622
621
|
return os.path.join(paths)
|
|
623
622
|
if is_sequence(paths):
|
|
624
623
|
return os.path.join(*paths)
|
|
@@ -810,7 +809,7 @@ class FilterModule(object):
|
|
|
810
809
|
'dict2items': dict_to_list_of_dict_key_value_elements,
|
|
811
810
|
'items2dict': list_of_dict_key_value_elements_to_dict,
|
|
812
811
|
'subelements': subelements,
|
|
813
|
-
'split': partial(unicode_wrap,
|
|
812
|
+
'split': partial(unicode_wrap, str.split),
|
|
814
813
|
# FDI038 - replace this with a standard type compat shim
|
|
815
814
|
'groupby': _cleansed_groupby,
|
|
816
815
|
|