ansible-core 2.18.5rc1__py3-none-any.whl → 2.19.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.
- ansible/_internal/__init__.py +53 -0
- ansible/_internal/_ansiballz.py +265 -0
- ansible/_internal/_collection_proxy.py +47 -0
- ansible/_internal/_datatag/__init__.py +0 -0
- ansible/_internal/_datatag/_tags.py +130 -0
- ansible/_internal/_datatag/_utils.py +19 -0
- ansible/_internal/_datatag/_wrappers.py +33 -0
- ansible/_internal/_errors/__init__.py +0 -0
- ansible/_internal/_errors/_captured.py +128 -0
- ansible/_internal/_errors/_handler.py +91 -0
- ansible/_internal/_errors/_utils.py +310 -0
- ansible/_internal/_json/__init__.py +203 -0
- ansible/_internal/_json/_legacy_encoder.py +34 -0
- ansible/_internal/_json/_profiles/__init__.py +0 -0
- ansible/_internal/_json/_profiles/_cache_persistence.py +55 -0
- ansible/_internal/_json/_profiles/_inventory_legacy.py +40 -0
- ansible/_internal/_json/_profiles/_legacy.py +197 -0
- ansible/_internal/_locking.py +21 -0
- ansible/_internal/_plugins/__init__.py +0 -0
- ansible/_internal/_plugins/_cache.py +57 -0
- ansible/_internal/_task.py +78 -0
- ansible/_internal/_templating/__init__.py +10 -0
- ansible/_internal/_templating/_access.py +86 -0
- ansible/_internal/_templating/_chain_templar.py +63 -0
- ansible/_internal/_templating/_datatag.py +95 -0
- ansible/_internal/_templating/_engine.py +588 -0
- ansible/_internal/_templating/_errors.py +28 -0
- ansible/_internal/_templating/_jinja_bits.py +1066 -0
- ansible/_internal/_templating/_jinja_common.py +332 -0
- ansible/_internal/_templating/_jinja_patches.py +44 -0
- ansible/_internal/_templating/_jinja_plugins.py +345 -0
- ansible/_internal/_templating/_lazy_containers.py +633 -0
- ansible/_internal/_templating/_marker_behaviors.py +103 -0
- ansible/_internal/_templating/_transform.py +63 -0
- ansible/_internal/_templating/_utils.py +107 -0
- ansible/_internal/_wrapt.py +1052 -0
- ansible/_internal/_yaml/__init__.py +0 -0
- ansible/_internal/_yaml/_constructor.py +240 -0
- ansible/_internal/_yaml/_dumper.py +62 -0
- ansible/_internal/_yaml/_errors.py +166 -0
- ansible/_internal/_yaml/_loader.py +66 -0
- ansible/_internal/ansible_collections/ansible/_protomatter/README.md +11 -0
- ansible/_internal/ansible_collections/ansible/_protomatter/plugins/action/debug.py +36 -0
- ansible/_internal/ansible_collections/ansible/_protomatter/plugins/filter/apply_trust.py +19 -0
- ansible/_internal/ansible_collections/ansible/_protomatter/plugins/filter/dump_object.py +18 -0
- ansible/_internal/ansible_collections/ansible/_protomatter/plugins/filter/finalize.py +16 -0
- ansible/_internal/ansible_collections/ansible/_protomatter/plugins/filter/origin.py +18 -0
- ansible/_internal/ansible_collections/ansible/_protomatter/plugins/filter/python_literal_eval.py +24 -0
- ansible/_internal/ansible_collections/ansible/_protomatter/plugins/filter/python_literal_eval.yml +33 -0
- ansible/_internal/ansible_collections/ansible/_protomatter/plugins/filter/tag_names.py +16 -0
- ansible/_internal/ansible_collections/ansible/_protomatter/plugins/filter/true_type.py +17 -0
- ansible/_internal/ansible_collections/ansible/_protomatter/plugins/filter/unmask.py +49 -0
- ansible/_internal/ansible_collections/ansible/_protomatter/plugins/lookup/config.py +21 -0
- ansible/_internal/ansible_collections/ansible/_protomatter/plugins/lookup/config.yml +2 -0
- ansible/_internal/ansible_collections/ansible/_protomatter/plugins/test/tagged.py +15 -0
- ansible/_internal/ansible_collections/ansible/_protomatter/plugins/test/tagged.yml +19 -0
- ansible/_internal/ansible_collections/ansible/_protomatter/plugins/test/tagged_with.py +18 -0
- ansible/_internal/ansible_collections/ansible/_protomatter/plugins/test/tagged_with.yml +19 -0
- ansible/cli/__init__.py +159 -89
- ansible/cli/_ssh_askpass.py +47 -0
- ansible/cli/adhoc.py +14 -7
- ansible/cli/arguments/option_helpers.py +154 -7
- ansible/cli/config.py +43 -68
- ansible/cli/console.py +10 -8
- ansible/cli/doc.py +62 -53
- ansible/cli/galaxy.py +27 -20
- ansible/cli/inventory.py +28 -26
- ansible/cli/playbook.py +4 -12
- ansible/cli/pull.py +51 -11
- ansible/cli/scripts/ansible_connection_cli_stub.py +7 -7
- ansible/cli/vault.py +12 -11
- ansible/compat/__init__.py +2 -2
- ansible/config/base.yml +166 -112
- ansible/config/manager.py +52 -49
- ansible/constants.py +3 -4
- ansible/errors/__init__.py +277 -235
- ansible/executor/interpreter_discovery.py +28 -149
- ansible/executor/module_common.py +426 -493
- ansible/executor/play_iterator.py +22 -27
- ansible/executor/playbook_executor.py +11 -11
- ansible/executor/powershell/async_watchdog.ps1 +97 -102
- ansible/executor/powershell/async_wrapper.ps1 +202 -151
- ansible/executor/powershell/become_wrapper.ps1 +89 -144
- ansible/executor/powershell/bootstrap_wrapper.ps1 +24 -9
- ansible/executor/powershell/coverage_wrapper.ps1 +82 -135
- ansible/executor/powershell/exec_wrapper.ps1 +462 -196
- ansible/executor/powershell/module_manifest.py +417 -265
- ansible/executor/powershell/module_wrapper.ps1 +169 -186
- ansible/executor/powershell/psrp_fetch_file.ps1 +41 -0
- ansible/executor/powershell/psrp_put_file.ps1 +122 -0
- ansible/executor/powershell/winrm_fetch_file.ps1 +46 -0
- ansible/executor/powershell/winrm_put_file.ps1 +36 -0
- ansible/executor/process/worker.py +161 -96
- ansible/executor/stats.py +5 -5
- ansible/executor/task_executor.py +268 -258
- ansible/executor/task_queue_manager.py +124 -90
- ansible/executor/task_result.py +183 -78
- ansible/galaxy/__init__.py +2 -2
- ansible/galaxy/api.py +22 -18
- ansible/galaxy/collection/__init__.py +1 -1
- ansible/galaxy/collection/concrete_artifact_manager.py +8 -11
- ansible/galaxy/dependency_resolution/dataclasses.py +14 -4
- ansible/galaxy/dependency_resolution/providers.py +1 -1
- ansible/galaxy/dependency_resolution/reporters.py +81 -0
- ansible/galaxy/role.py +4 -8
- ansible/galaxy/token.py +28 -21
- ansible/inventory/data.py +47 -57
- ansible/inventory/group.py +44 -72
- ansible/inventory/helpers.py +9 -0
- ansible/inventory/host.py +32 -54
- ansible/inventory/manager.py +78 -34
- ansible/keyword_desc.yml +1 -1
- ansible/module_utils/_internal/__init__.py +55 -0
- ansible/module_utils/_internal/_ambient_context.py +58 -0
- ansible/module_utils/_internal/_ansiballz.py +133 -0
- ansible/module_utils/_internal/_concurrent/_daemon_threading.py +1 -0
- ansible/module_utils/_internal/_dataclass_annotation_patch.py +64 -0
- ansible/module_utils/_internal/_dataclass_validation.py +217 -0
- ansible/module_utils/_internal/_datatag/__init__.py +928 -0
- ansible/module_utils/_internal/_datatag/_tags.py +38 -0
- ansible/module_utils/_internal/_debugging.py +31 -0
- ansible/module_utils/_internal/_errors.py +30 -0
- ansible/module_utils/_internal/_json/__init__.py +63 -0
- ansible/module_utils/_internal/_json/_legacy_encoder.py +26 -0
- ansible/module_utils/_internal/_json/_profiles/__init__.py +410 -0
- ansible/module_utils/_internal/_json/_profiles/_fallback_to_str.py +73 -0
- ansible/module_utils/_internal/_json/_profiles/_module_legacy_c2m.py +31 -0
- ansible/module_utils/_internal/_json/_profiles/_module_legacy_m2c.py +35 -0
- ansible/module_utils/_internal/_json/_profiles/_module_modern_c2m.py +35 -0
- ansible/module_utils/_internal/_json/_profiles/_module_modern_m2c.py +33 -0
- ansible/module_utils/_internal/_json/_profiles/_tagless.py +50 -0
- ansible/module_utils/_internal/_patches/__init__.py +66 -0
- ansible/module_utils/_internal/_patches/_dataclass_annotation_patch.py +55 -0
- ansible/module_utils/_internal/_patches/_socket_patch.py +34 -0
- ansible/module_utils/_internal/_patches/_sys_intern_patch.py +34 -0
- ansible/module_utils/_internal/_plugin_exec_context.py +49 -0
- ansible/module_utils/_internal/_testing.py +0 -0
- ansible/module_utils/_internal/_traceback.py +89 -0
- ansible/module_utils/ansible_release.py +2 -2
- ansible/module_utils/api.py +1 -2
- ansible/module_utils/basic.py +152 -120
- ansible/module_utils/common/_utils.py +24 -28
- ansible/module_utils/common/collections.py +1 -2
- ansible/module_utils/common/dict_transformations.py +2 -2
- ansible/module_utils/common/file.py +2 -2
- ansible/module_utils/common/json.py +90 -84
- ansible/module_utils/common/locale.py +2 -2
- ansible/module_utils/common/messages.py +108 -0
- ansible/module_utils/common/parameters.py +27 -24
- ansible/module_utils/common/process.py +2 -2
- ansible/module_utils/common/respawn.py +41 -19
- ansible/module_utils/common/sentinel.py +66 -0
- ansible/module_utils/common/sys_info.py +8 -8
- ansible/module_utils/common/text/converters.py +16 -37
- ansible/module_utils/common/validation.py +35 -24
- ansible/module_utils/common/warnings.py +86 -25
- ansible/module_utils/common/yaml.py +29 -3
- ansible/module_utils/compat/datetime.py +33 -21
- ansible/module_utils/compat/paramiko.py +21 -10
- ansible/module_utils/compat/typing.py +6 -5
- ansible/module_utils/connection.py +2 -2
- ansible/module_utils/csharp/Ansible.Basic.cs +14 -11
- ansible/module_utils/csharp/Ansible.Become.cs +1 -0
- ansible/module_utils/csharp/Ansible._Async.cs +517 -0
- ansible/module_utils/datatag.py +46 -0
- ansible/module_utils/distro/__init__.py +2 -2
- ansible/module_utils/facts/ansible_collector.py +4 -5
- ansible/module_utils/facts/collector.py +13 -14
- ansible/module_utils/facts/compat.py +4 -4
- ansible/module_utils/facts/default_collectors.py +1 -1
- ansible/module_utils/facts/hardware/aix.py +34 -0
- ansible/module_utils/facts/hardware/base.py +1 -1
- ansible/module_utils/facts/hardware/darwin.py +1 -3
- ansible/module_utils/facts/hardware/freebsd.py +2 -2
- ansible/module_utils/facts/hardware/linux.py +4 -4
- ansible/module_utils/facts/namespace.py +1 -1
- ansible/module_utils/facts/network/base.py +1 -1
- ansible/module_utils/facts/network/fc_wwn.py +1 -2
- ansible/module_utils/facts/network/iscsi.py +1 -2
- ansible/module_utils/facts/network/nvme.py +1 -2
- ansible/module_utils/facts/other/facter.py +1 -2
- ansible/module_utils/facts/other/ohai.py +2 -3
- ansible/module_utils/facts/system/apparmor.py +1 -2
- ansible/module_utils/facts/system/caps.py +1 -1
- ansible/module_utils/facts/system/chroot.py +1 -2
- ansible/module_utils/facts/system/cmdline.py +1 -2
- ansible/module_utils/facts/system/date_time.py +5 -3
- ansible/module_utils/facts/system/distribution.py +9 -8
- ansible/module_utils/facts/system/dns.py +1 -1
- ansible/module_utils/facts/system/env.py +1 -2
- ansible/module_utils/facts/system/fips.py +7 -20
- ansible/module_utils/facts/system/loadavg.py +1 -2
- ansible/module_utils/facts/system/local.py +1 -2
- ansible/module_utils/facts/system/lsb.py +1 -2
- ansible/module_utils/facts/system/pkg_mgr.py +1 -2
- ansible/module_utils/facts/system/platform.py +1 -2
- ansible/module_utils/facts/system/python.py +1 -2
- ansible/module_utils/facts/system/selinux.py +1 -1
- ansible/module_utils/facts/system/service_mgr.py +1 -2
- ansible/module_utils/facts/system/ssh_pub_keys.py +1 -1
- ansible/module_utils/facts/system/systemd.py +1 -1
- ansible/module_utils/facts/system/user.py +1 -2
- ansible/module_utils/facts/utils.py +3 -3
- ansible/module_utils/facts/virtual/base.py +1 -1
- ansible/module_utils/facts/virtual/sunos.py +3 -15
- ansible/module_utils/facts/virtual/sysctl.py +3 -16
- ansible/module_utils/json_utils.py +2 -2
- ansible/module_utils/parsing/convert_bool.py +1 -1
- ansible/module_utils/service.py +18 -21
- ansible/module_utils/splitter.py +7 -7
- ansible/module_utils/testing.py +31 -0
- ansible/module_utils/urls.py +60 -31
- ansible/modules/add_host.py +4 -4
- ansible/modules/apt.py +60 -46
- ansible/modules/apt_key.py +19 -12
- ansible/modules/apt_repository.py +19 -16
- ansible/modules/assemble.py +6 -6
- ansible/modules/assert.py +4 -4
- ansible/modules/async_status.py +10 -12
- ansible/modules/async_wrapper.py +8 -3
- ansible/modules/blockinfile.py +6 -7
- ansible/modules/command.py +10 -17
- ansible/modules/copy.py +57 -144
- ansible/modules/cron.py +20 -15
- ansible/modules/deb822_repository.py +8 -9
- ansible/modules/debconf.py +5 -5
- ansible/modules/debug.py +4 -4
- ansible/modules/dnf.py +8 -8
- ansible/modules/dnf5.py +39 -13
- ansible/modules/dpkg_selections.py +4 -4
- ansible/modules/expect.py +8 -10
- ansible/modules/fail.py +4 -4
- ansible/modules/fetch.py +4 -4
- ansible/modules/file.py +174 -133
- ansible/modules/find.py +19 -17
- ansible/modules/gather_facts.py +3 -3
- ansible/modules/get_url.py +59 -53
- ansible/modules/getent.py +7 -9
- ansible/modules/git.py +28 -25
- ansible/modules/group.py +6 -6
- ansible/modules/group_by.py +4 -4
- ansible/modules/hostname.py +13 -29
- ansible/modules/import_playbook.py +6 -6
- ansible/modules/import_role.py +6 -6
- ansible/modules/import_tasks.py +6 -6
- ansible/modules/include_role.py +6 -6
- ansible/modules/include_tasks.py +6 -6
- ansible/modules/include_vars.py +6 -6
- ansible/modules/iptables.py +86 -73
- ansible/modules/known_hosts.py +10 -10
- ansible/modules/lineinfile.py +5 -5
- ansible/modules/meta.py +4 -4
- ansible/modules/mount_facts.py +2 -2
- ansible/modules/package.py +4 -4
- ansible/modules/package_facts.py +22 -10
- ansible/modules/pause.py +6 -6
- ansible/modules/ping.py +6 -6
- ansible/modules/pip.py +10 -11
- ansible/modules/raw.py +4 -4
- ansible/modules/reboot.py +6 -6
- ansible/modules/replace.py +9 -13
- ansible/modules/rpm_key.py +7 -8
- ansible/modules/script.py +4 -4
- ansible/modules/service.py +7 -8
- ansible/modules/service_facts.py +87 -10
- ansible/modules/set_fact.py +5 -5
- ansible/modules/set_stats.py +4 -4
- ansible/modules/setup.py +2 -2
- ansible/modules/shell.py +6 -6
- ansible/modules/slurp.py +6 -6
- ansible/modules/stat.py +9 -23
- ansible/modules/subversion.py +15 -15
- ansible/modules/systemd.py +6 -6
- ansible/modules/systemd_service.py +6 -6
- ansible/modules/sysvinit.py +6 -6
- ansible/modules/tempfile.py +5 -6
- ansible/modules/template.py +6 -6
- ansible/modules/unarchive.py +32 -11
- ansible/modules/uri.py +33 -26
- ansible/modules/user.py +53 -34
- ansible/modules/validate_argument_spec.py +10 -7
- ansible/modules/wait_for.py +32 -27
- ansible/modules/wait_for_connection.py +6 -6
- ansible/modules/yum_repository.py +6 -6
- ansible/parsing/ajson.py +14 -32
- ansible/parsing/dataloader.py +99 -54
- ansible/parsing/mod_args.py +28 -44
- ansible/parsing/plugin_docs.py +21 -86
- ansible/parsing/quoting.py +1 -1
- ansible/parsing/splitter.py +27 -12
- ansible/parsing/utils/addresses.py +24 -24
- ansible/parsing/utils/jsonify.py +5 -1
- ansible/parsing/utils/yaml.py +32 -61
- ansible/parsing/vault/__init__.py +319 -87
- ansible/parsing/yaml/__init__.py +0 -18
- ansible/parsing/yaml/dumper.py +6 -120
- ansible/parsing/yaml/loader.py +6 -39
- ansible/parsing/yaml/objects.py +43 -335
- ansible/playbook/__init__.py +1 -1
- ansible/playbook/attribute.py +8 -3
- ansible/playbook/base.py +182 -132
- ansible/playbook/block.py +26 -24
- ansible/playbook/collectionsearch.py +1 -15
- ansible/playbook/conditional.py +3 -77
- ansible/playbook/handler.py +8 -2
- ansible/playbook/helpers.py +41 -53
- ansible/playbook/included_file.py +31 -27
- ansible/playbook/loop_control.py +2 -2
- ansible/playbook/play.py +85 -44
- ansible/playbook/play_context.py +12 -17
- ansible/playbook/playbook_include.py +14 -15
- ansible/playbook/role/__init__.py +24 -26
- ansible/playbook/role/definition.py +15 -17
- ansible/playbook/role/include.py +2 -4
- ansible/playbook/role/metadata.py +10 -11
- ansible/playbook/role_include.py +3 -3
- ansible/playbook/taggable.py +13 -8
- ansible/playbook/task.py +188 -118
- ansible/playbook/task_include.py +5 -5
- ansible/plugins/__init__.py +68 -21
- ansible/plugins/action/__init__.py +209 -176
- ansible/plugins/action/add_host.py +1 -1
- ansible/plugins/action/assemble.py +1 -1
- ansible/plugins/action/assert.py +54 -66
- ansible/plugins/action/copy.py +7 -11
- ansible/plugins/action/debug.py +37 -31
- ansible/plugins/action/dnf.py +3 -4
- ansible/plugins/action/fail.py +1 -1
- ansible/plugins/action/fetch.py +4 -5
- ansible/plugins/action/gather_facts.py +7 -6
- ansible/plugins/action/group_by.py +1 -1
- ansible/plugins/action/include_vars.py +10 -11
- ansible/plugins/action/package.py +3 -6
- ansible/plugins/action/pause.py +2 -2
- ansible/plugins/action/script.py +15 -8
- ansible/plugins/action/service.py +6 -11
- ansible/plugins/action/set_fact.py +3 -12
- ansible/plugins/action/set_stats.py +3 -8
- ansible/plugins/action/template.py +35 -59
- ansible/plugins/action/unarchive.py +1 -1
- ansible/plugins/action/validate_argument_spec.py +5 -5
- ansible/plugins/action/wait_for_connection.py +1 -1
- ansible/plugins/become/__init__.py +31 -8
- ansible/plugins/become/runas.py +71 -0
- ansible/plugins/become/su.py +13 -8
- ansible/plugins/become/sudo.py +19 -0
- ansible/plugins/cache/__init__.py +35 -44
- ansible/plugins/cache/base.py +8 -0
- ansible/plugins/cache/jsonfile.py +10 -16
- ansible/plugins/cache/memory.py +6 -12
- ansible/plugins/callback/__init__.py +284 -179
- ansible/plugins/callback/default.py +99 -92
- ansible/plugins/callback/junit.py +44 -39
- ansible/plugins/callback/minimal.py +28 -25
- ansible/plugins/callback/oneline.py +28 -21
- ansible/plugins/callback/tree.py +16 -11
- ansible/plugins/connection/__init__.py +47 -34
- ansible/plugins/connection/local.py +150 -54
- ansible/plugins/connection/paramiko_ssh.py +21 -18
- ansible/plugins/connection/psrp.py +76 -165
- ansible/plugins/connection/ssh.py +301 -78
- ansible/plugins/connection/winrm.py +58 -140
- ansible/plugins/doc_fragments/action_common_attributes.py +14 -14
- ansible/plugins/doc_fragments/action_core.py +6 -6
- ansible/plugins/doc_fragments/backup.py +2 -2
- ansible/plugins/doc_fragments/checksum_common.py +27 -0
- ansible/plugins/doc_fragments/constructed.py +6 -2
- ansible/plugins/doc_fragments/decrypt.py +2 -2
- ansible/plugins/doc_fragments/default_callback.py +2 -2
- ansible/plugins/doc_fragments/files.py +2 -2
- ansible/plugins/doc_fragments/inventory_cache.py +2 -2
- ansible/plugins/doc_fragments/result_format_callback.py +2 -2
- ansible/plugins/doc_fragments/return_common.py +2 -2
- ansible/plugins/doc_fragments/template_common.py +4 -4
- ansible/plugins/doc_fragments/url.py +17 -1
- ansible/plugins/doc_fragments/url_windows.py +2 -2
- ansible/plugins/doc_fragments/validate.py +2 -2
- ansible/plugins/doc_fragments/vars_plugin_staging.py +2 -2
- ansible/plugins/filter/__init__.py +6 -2
- ansible/plugins/filter/b64decode.yml +22 -0
- ansible/plugins/filter/b64encode.yml +22 -0
- ansible/plugins/filter/bool.yml +11 -4
- ansible/plugins/filter/core.py +225 -108
- ansible/plugins/filter/encryption.py +32 -32
- ansible/plugins/filter/flatten.yml +3 -2
- ansible/plugins/filter/human_to_bytes.yml +1 -1
- ansible/plugins/filter/mathstuff.py +30 -37
- ansible/plugins/filter/password_hash.yml +8 -0
- ansible/plugins/filter/regex_search.yml +1 -4
- ansible/plugins/filter/split.yml +1 -1
- ansible/plugins/filter/to_nice_yaml.yml +0 -4
- ansible/plugins/filter/to_yaml.yml +0 -4
- ansible/plugins/filter/unvault.yml +1 -1
- ansible/plugins/filter/urls.py +1 -1
- ansible/plugins/filter/urlsplit.py +8 -9
- ansible/plugins/filter/vault.yml +14 -9
- ansible/plugins/filter/win_basename.yml +6 -1
- ansible/plugins/filter/win_dirname.yml +5 -0
- ansible/plugins/inventory/__init__.py +97 -77
- ansible/plugins/inventory/advanced_host_list.py +7 -5
- ansible/plugins/inventory/auto.py +11 -4
- ansible/plugins/inventory/constructed.py +21 -24
- ansible/plugins/inventory/generator.py +16 -11
- ansible/plugins/inventory/host_list.py +7 -5
- ansible/plugins/inventory/ini.py +78 -44
- ansible/plugins/inventory/script.py +189 -119
- ansible/plugins/inventory/toml.py +16 -126
- ansible/plugins/inventory/yaml.py +10 -8
- ansible/plugins/list.py +3 -3
- ansible/plugins/loader.py +197 -82
- ansible/plugins/lookup/__init__.py +21 -4
- ansible/plugins/lookup/config.py +21 -35
- ansible/plugins/lookup/csvfile.py +3 -2
- ansible/plugins/lookup/dict.py +1 -6
- ansible/plugins/lookup/env.py +12 -9
- ansible/plugins/lookup/file.py +5 -8
- ansible/plugins/lookup/first_found.py +86 -55
- ansible/plugins/lookup/indexed_items.py +1 -10
- ansible/plugins/lookup/ini.py +14 -13
- ansible/plugins/lookup/items.py +1 -1
- ansible/plugins/lookup/lines.py +8 -1
- ansible/plugins/lookup/list.py +1 -1
- ansible/plugins/lookup/nested.py +2 -18
- ansible/plugins/lookup/password.py +5 -5
- ansible/plugins/lookup/pipe.py +5 -7
- ansible/plugins/lookup/sequence.py +18 -8
- ansible/plugins/lookup/subelements.py +1 -4
- ansible/plugins/lookup/template.py +42 -36
- ansible/plugins/lookup/together.py +0 -12
- ansible/plugins/lookup/unvault.py +1 -5
- ansible/plugins/lookup/url.py +2 -8
- ansible/plugins/lookup/vars.py +16 -24
- ansible/plugins/shell/__init__.py +2 -2
- ansible/plugins/shell/cmd.py +2 -2
- ansible/plugins/shell/powershell.py +39 -22
- ansible/plugins/shell/sh.py +3 -2
- ansible/plugins/strategy/__init__.py +159 -184
- ansible/plugins/strategy/debug.py +2 -2
- ansible/plugins/strategy/free.py +16 -31
- ansible/plugins/strategy/host_pinned.py +2 -2
- ansible/plugins/strategy/linear.py +41 -41
- ansible/plugins/terminal/__init__.py +4 -4
- ansible/plugins/test/__init__.py +7 -2
- ansible/plugins/test/core.py +55 -21
- ansible/plugins/test/files.py +1 -1
- ansible/plugins/test/mathstuff.py +3 -3
- ansible/plugins/test/uri.py +3 -3
- ansible/plugins/vars/host_group_vars.py +7 -14
- ansible/release.py +2 -2
- ansible/template/__init__.py +370 -944
- ansible/utils/__init__.py +0 -18
- ansible/utils/_ssh_agent.py +657 -0
- ansible/utils/collection_loader/__init__.py +52 -5
- ansible/utils/collection_loader/_collection_config.py +5 -6
- ansible/utils/collection_loader/_collection_finder.py +79 -93
- ansible/utils/collection_loader/_collection_meta.py +13 -8
- ansible/utils/display.py +433 -63
- ansible/utils/encrypt.py +27 -19
- ansible/utils/fqcn.py +2 -2
- ansible/utils/hashing.py +2 -2
- ansible/utils/helpers.py +2 -2
- ansible/utils/listify.py +8 -8
- ansible/utils/lock.py +2 -2
- ansible/utils/path.py +4 -4
- ansible/utils/plugin_docs.py +14 -13
- ansible/utils/sentinel.py +4 -62
- ansible/utils/singleton.py +2 -0
- ansible/utils/ssh_functions.py +1 -1
- ansible/utils/unsafe_proxy.py +23 -332
- ansible/utils/vars.py +51 -8
- ansible/utils/version.py +2 -2
- ansible/vars/clean.py +5 -5
- ansible/vars/hostvars.py +60 -90
- ansible/vars/manager.py +206 -282
- ansible/vars/reserved.py +8 -9
- ansible_core-2.19.0b2.dist-info/BSD-3-Clause.txt +28 -0
- {ansible_core-2.18.5rc1.dist-info → ansible_core-2.19.0b2.dist-info}/METADATA +5 -4
- ansible_core-2.19.0b2.dist-info/RECORD +1072 -0
- {ansible_core-2.18.5rc1.dist-info → ansible_core-2.19.0b2.dist-info}/WHEEL +1 -1
- ansible_test/_data/completion/docker.txt +7 -7
- ansible_test/_data/completion/remote.txt +6 -6
- ansible_test/_data/completion/windows.txt +1 -0
- ansible_test/_data/requirements/ansible.txt +2 -2
- ansible_test/_data/requirements/sanity.ansible-doc.txt +3 -3
- ansible_test/_data/requirements/sanity.changelog.txt +1 -1
- ansible_test/_data/requirements/sanity.import.plugin.txt +2 -2
- ansible_test/_data/requirements/sanity.pylint.txt +4 -4
- ansible_test/_data/requirements/sanity.validate-modules.txt +2 -2
- ansible_test/_data/requirements/sanity.yamllint.txt +1 -1
- ansible_test/_data/requirements/units.txt +1 -0
- ansible_test/_internal/__init__.py +1 -0
- ansible_test/_internal/ansible_util.py +2 -0
- ansible_test/_internal/become.py +1 -0
- ansible_test/_internal/bootstrap.py +1 -0
- ansible_test/_internal/cache.py +1 -0
- ansible_test/_internal/cgroup.py +1 -0
- ansible_test/_internal/ci/__init__.py +1 -0
- ansible_test/_internal/ci/azp.py +1 -0
- ansible_test/_internal/ci/local.py +1 -0
- ansible_test/_internal/classification/__init__.py +1 -0
- ansible_test/_internal/classification/common.py +1 -0
- ansible_test/_internal/classification/csharp.py +1 -0
- ansible_test/_internal/classification/powershell.py +1 -0
- ansible_test/_internal/classification/python.py +1 -0
- ansible_test/_internal/cli/__init__.py +1 -0
- ansible_test/_internal/cli/actions.py +1 -0
- ansible_test/_internal/cli/argparsing/__init__.py +1 -0
- ansible_test/_internal/cli/argparsing/actions.py +1 -0
- ansible_test/_internal/cli/argparsing/argcompletion.py +1 -0
- ansible_test/_internal/cli/argparsing/parsers.py +1 -0
- ansible_test/_internal/cli/commands/__init__.py +11 -0
- ansible_test/_internal/cli/commands/coverage/__init__.py +1 -0
- ansible_test/_internal/cli/commands/coverage/analyze/__init__.py +1 -0
- ansible_test/_internal/cli/commands/coverage/analyze/targets/__init__.py +1 -0
- ansible_test/_internal/cli/commands/coverage/analyze/targets/combine.py +1 -0
- ansible_test/_internal/cli/commands/coverage/analyze/targets/expand.py +1 -0
- ansible_test/_internal/cli/commands/coverage/analyze/targets/filter.py +1 -0
- ansible_test/_internal/cli/commands/coverage/analyze/targets/generate.py +1 -0
- ansible_test/_internal/cli/commands/coverage/analyze/targets/missing.py +1 -0
- ansible_test/_internal/cli/commands/coverage/combine.py +1 -0
- ansible_test/_internal/cli/commands/coverage/erase.py +1 -0
- ansible_test/_internal/cli/commands/coverage/html.py +1 -0
- ansible_test/_internal/cli/commands/coverage/report.py +1 -0
- ansible_test/_internal/cli/commands/coverage/xml.py +1 -0
- ansible_test/_internal/cli/commands/env.py +1 -0
- ansible_test/_internal/cli/commands/integration/__init__.py +1 -0
- ansible_test/_internal/cli/commands/integration/network.py +1 -0
- ansible_test/_internal/cli/commands/integration/posix.py +1 -0
- ansible_test/_internal/cli/commands/integration/windows.py +1 -0
- ansible_test/_internal/cli/commands/sanity.py +9 -0
- ansible_test/_internal/cli/commands/shell.py +1 -0
- ansible_test/_internal/cli/commands/units.py +1 -0
- ansible_test/_internal/cli/compat.py +1 -0
- ansible_test/_internal/cli/completers.py +1 -0
- ansible_test/_internal/cli/converters.py +1 -0
- ansible_test/_internal/cli/environments.py +1 -0
- ansible_test/_internal/cli/epilog.py +1 -0
- ansible_test/_internal/cli/parsers/__init__.py +1 -0
- ansible_test/_internal/cli/parsers/base_argument_parsers.py +1 -0
- ansible_test/_internal/cli/parsers/helpers.py +1 -0
- ansible_test/_internal/cli/parsers/host_config_parsers.py +1 -0
- ansible_test/_internal/cli/parsers/key_value_parsers.py +1 -0
- ansible_test/_internal/cli/parsers/value_parsers.py +1 -0
- ansible_test/_internal/commands/__init__.py +1 -0
- ansible_test/_internal/commands/coverage/__init__.py +2 -1
- ansible_test/_internal/commands/coverage/analyze/__init__.py +1 -0
- ansible_test/_internal/commands/coverage/analyze/targets/__init__.py +1 -0
- ansible_test/_internal/commands/coverage/analyze/targets/combine.py +1 -0
- ansible_test/_internal/commands/coverage/analyze/targets/expand.py +1 -0
- ansible_test/_internal/commands/coverage/analyze/targets/filter.py +1 -0
- ansible_test/_internal/commands/coverage/analyze/targets/generate.py +1 -0
- ansible_test/_internal/commands/coverage/analyze/targets/missing.py +1 -0
- ansible_test/_internal/commands/coverage/combine.py +2 -1
- ansible_test/_internal/commands/coverage/erase.py +1 -0
- ansible_test/_internal/commands/coverage/html.py +1 -0
- ansible_test/_internal/commands/coverage/report.py +1 -0
- ansible_test/_internal/commands/coverage/xml.py +1 -0
- ansible_test/_internal/commands/env/__init__.py +2 -0
- ansible_test/_internal/commands/integration/__init__.py +4 -0
- ansible_test/_internal/commands/integration/cloud/__init__.py +1 -0
- ansible_test/_internal/commands/integration/cloud/acme.py +2 -1
- ansible_test/_internal/commands/integration/cloud/aws.py +1 -0
- ansible_test/_internal/commands/integration/cloud/azure.py +1 -0
- ansible_test/_internal/commands/integration/cloud/cs.py +1 -0
- ansible_test/_internal/commands/integration/cloud/digitalocean.py +1 -0
- ansible_test/_internal/commands/integration/cloud/galaxy.py +3 -2
- ansible_test/_internal/commands/integration/cloud/hcloud.py +1 -0
- ansible_test/_internal/commands/integration/cloud/httptester.py +2 -1
- ansible_test/_internal/commands/integration/cloud/nios.py +2 -1
- ansible_test/_internal/commands/integration/cloud/opennebula.py +1 -0
- ansible_test/_internal/commands/integration/cloud/openshift.py +1 -0
- ansible_test/_internal/commands/integration/cloud/scaleway.py +1 -0
- ansible_test/_internal/commands/integration/cloud/vcenter.py +1 -0
- ansible_test/_internal/commands/integration/cloud/vultr.py +1 -0
- ansible_test/_internal/commands/integration/coverage.py +1 -0
- ansible_test/_internal/commands/integration/filters.py +1 -0
- ansible_test/_internal/commands/integration/network.py +1 -0
- ansible_test/_internal/commands/integration/posix.py +1 -0
- ansible_test/_internal/commands/integration/windows.py +1 -0
- ansible_test/_internal/commands/sanity/__init__.py +16 -1
- ansible_test/_internal/commands/sanity/ansible_doc.py +1 -0
- ansible_test/_internal/commands/sanity/bin_symlinks.py +1 -0
- ansible_test/_internal/commands/sanity/compile.py +1 -0
- ansible_test/_internal/commands/sanity/ignores.py +1 -0
- ansible_test/_internal/commands/sanity/import.py +1 -0
- ansible_test/_internal/commands/sanity/integration_aliases.py +1 -0
- ansible_test/_internal/commands/sanity/pep8.py +1 -0
- ansible_test/_internal/commands/sanity/pslint.py +1 -0
- ansible_test/_internal/commands/sanity/pylint.py +24 -26
- ansible_test/_internal/commands/sanity/shellcheck.py +1 -0
- ansible_test/_internal/commands/sanity/validate_modules.py +1 -0
- ansible_test/_internal/commands/sanity/yamllint.py +1 -0
- ansible_test/_internal/commands/shell/__init__.py +1 -0
- ansible_test/_internal/commands/units/__init__.py +1 -0
- ansible_test/_internal/compat/__init__.py +1 -0
- ansible_test/_internal/compat/packaging.py +1 -0
- ansible_test/_internal/compat/yaml.py +1 -0
- ansible_test/_internal/completion.py +1 -0
- ansible_test/_internal/config.py +2 -0
- ansible_test/_internal/connections.py +1 -0
- ansible_test/_internal/constants.py +1 -0
- ansible_test/_internal/containers.py +1 -0
- ansible_test/_internal/content_config.py +1 -0
- ansible_test/_internal/core_ci.py +1 -0
- ansible_test/_internal/coverage_util.py +11 -10
- ansible_test/_internal/data.py +1 -0
- ansible_test/_internal/delegation.py +1 -0
- ansible_test/_internal/dev/__init__.py +1 -0
- ansible_test/_internal/dev/container_probe.py +1 -0
- ansible_test/_internal/diff.py +3 -2
- ansible_test/_internal/docker_util.py +2 -1
- ansible_test/_internal/encoding.py +1 -0
- ansible_test/_internal/executor.py +1 -0
- ansible_test/_internal/git.py +1 -0
- ansible_test/_internal/host_configs.py +1 -0
- ansible_test/_internal/host_profiles.py +1 -0
- ansible_test/_internal/http.py +1 -0
- ansible_test/_internal/init.py +1 -0
- ansible_test/_internal/inventory.py +35 -3
- ansible_test/_internal/io.py +1 -0
- ansible_test/_internal/metadata.py +1 -0
- ansible_test/_internal/payload.py +1 -0
- ansible_test/_internal/provider/__init__.py +1 -0
- ansible_test/_internal/provider/layout/__init__.py +1 -0
- ansible_test/_internal/provider/layout/ansible.py +1 -0
- ansible_test/_internal/provider/layout/collection.py +1 -0
- ansible_test/_internal/provider/layout/unsupported.py +1 -0
- ansible_test/_internal/provider/source/__init__.py +1 -0
- ansible_test/_internal/provider/source/git.py +1 -0
- ansible_test/_internal/provider/source/installed.py +1 -0
- ansible_test/_internal/provider/source/unsupported.py +1 -0
- ansible_test/_internal/provider/source/unversioned.py +1 -0
- ansible_test/_internal/provisioning.py +1 -0
- ansible_test/_internal/pypi_proxy.py +6 -5
- ansible_test/_internal/python_requirements.py +1 -0
- ansible_test/_internal/ssh.py +1 -0
- ansible_test/_internal/target.py +1 -0
- ansible_test/_internal/test.py +3 -2
- ansible_test/_internal/thread.py +1 -0
- ansible_test/_internal/timeout.py +1 -0
- ansible_test/_internal/util.py +1 -0
- ansible_test/_internal/util_common.py +5 -2
- ansible_test/_internal/venv.py +1 -0
- ansible_test/_util/controller/sanity/code-smell/action-plugin-docs.py +1 -0
- ansible_test/_util/controller/sanity/code-smell/changelog/sphinx.py +1 -0
- ansible_test/_util/controller/sanity/code-smell/changelog.py +1 -0
- ansible_test/_util/controller/sanity/code-smell/empty-init.py +1 -0
- ansible_test/_util/controller/sanity/code-smell/line-endings.py +1 -0
- ansible_test/_util/controller/sanity/code-smell/no-assert.py +1 -0
- ansible_test/_util/controller/sanity/code-smell/no-get-exception.py +1 -0
- ansible_test/_util/controller/sanity/code-smell/no-illegal-filenames.py +1 -0
- ansible_test/_util/controller/sanity/code-smell/no-smart-quotes.py +1 -0
- ansible_test/_util/controller/sanity/code-smell/replace-urlopen.py +1 -0
- ansible_test/_util/controller/sanity/code-smell/runtime-metadata.py +28 -1
- ansible_test/_util/controller/sanity/code-smell/shebang.py +1 -0
- ansible_test/_util/controller/sanity/code-smell/symlinks.py +1 -0
- ansible_test/_util/controller/sanity/code-smell/use-argspec-type-path.py +1 -0
- ansible_test/_util/controller/sanity/code-smell/use-compat-six.py +1 -0
- ansible_test/_util/controller/sanity/integration-aliases/yaml_to_json.py +2 -1
- ansible_test/_util/controller/sanity/pep8/current-ignore.txt +4 -0
- ansible_test/_util/controller/sanity/pylint/config/ansible-test-target.cfg +7 -5
- ansible_test/_util/controller/sanity/pylint/config/ansible-test.cfg +7 -5
- ansible_test/_util/controller/sanity/pylint/config/code-smell.cfg +7 -5
- ansible_test/_util/controller/sanity/pylint/config/collection.cfg +3 -5
- ansible_test/_util/controller/sanity/pylint/config/default.cfg +7 -7
- ansible_test/_util/controller/sanity/pylint/plugins/deprecated.py +1 -13
- ansible_test/_util/controller/sanity/pylint/plugins/hide_unraisable.py +1 -0
- ansible_test/_util/controller/sanity/pylint/plugins/string_format.py +1 -8
- ansible_test/_util/controller/sanity/pylint/plugins/unwanted.py +1 -8
- ansible_test/_util/controller/sanity/validate-modules/validate_modules/main.py +55 -28
- ansible_test/_util/controller/sanity/validate-modules/validate_modules/module_args.py +12 -5
- ansible_test/_util/controller/sanity/validate-modules/validate_modules/schema.py +13 -2
- ansible_test/_util/controller/sanity/validate-modules/validate_modules/utils.py +1 -0
- ansible_test/_util/controller/sanity/yamllint/yamllinter.py +35 -17
- ansible_test/_util/controller/tools/collection_detail.py +1 -0
- ansible_test/_util/controller/tools/yaml_to_json.py +2 -1
- ansible_test/_util/target/pytest/plugins/ansible_forked.py +6 -1
- ansible_test/_util/target/pytest/plugins/ansible_pytest_collections.py +2 -1
- ansible_test/_util/target/pytest/plugins/ansible_pytest_coverage.py +1 -0
- ansible_test/_util/target/sanity/compile/compile.py +1 -0
- ansible_test/_util/target/sanity/import/importer.py +15 -16
- ansible_test/_util/target/setup/bootstrap.sh +9 -20
- ansible_test/_util/target/setup/probe_cgroups.py +1 -0
- ansible_test/_util/target/setup/quiet_pip.py +1 -0
- ansible_test/_util/target/setup/requirements.py +35 -27
- ansible_test/_util/target/tools/virtualenvcheck.py +2 -1
- ansible_test/_util/target/tools/yamlcheck.py +2 -1
- ansible/compat/selectors.py +0 -32
- ansible/errors/yaml_strings.py +0 -138
- ansible/executor/action_write_locks.py +0 -44
- ansible/executor/discovery/python_target.py +0 -47
- ansible/executor/powershell/module_powershell_wrapper.ps1 +0 -86
- ansible/executor/powershell/module_script_wrapper.ps1 +0 -22
- ansible/module_utils/compat/importlib.py +0 -26
- ansible/module_utils/compat/selectors.py +0 -32
- ansible/module_utils/pycompat24.py +0 -73
- ansible/parsing/yaml/constructor.py +0 -178
- ansible/template/native_helpers.py +0 -251
- ansible/template/template.py +0 -43
- ansible/template/vars.py +0 -77
- ansible/utils/native_jinja.py +0 -11
- ansible/vars/fact_cache.py +0 -71
- ansible_core-2.18.5rc1.dist-info/RECORD +0 -992
- {ansible_core-2.18.5rc1.dist-info → ansible_core-2.19.0b2.dist-info}/Apache-License.txt +0 -0
- {ansible_core-2.18.5rc1.dist-info → ansible_core-2.19.0b2.dist-info}/COPYING +0 -0
- {ansible_core-2.18.5rc1.dist-info → ansible_core-2.19.0b2.dist-info}/MIT-license.txt +0 -0
- {ansible_core-2.18.5rc1.dist-info → ansible_core-2.19.0b2.dist-info}/PSF-license.txt +0 -0
- {ansible_core-2.18.5rc1.dist-info → ansible_core-2.19.0b2.dist-info}/entry_points.txt +0 -0
- {ansible_core-2.18.5rc1.dist-info → ansible_core-2.19.0b2.dist-info}/simplified_bsd.txt +0 -0
- {ansible_core-2.18.5rc1.dist-info → ansible_core-2.19.0b2.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,928 @@
|
|
1
|
+
from __future__ import annotations
|
2
|
+
|
3
|
+
import abc
|
4
|
+
import collections.abc as c
|
5
|
+
import copy
|
6
|
+
import dataclasses
|
7
|
+
import datetime
|
8
|
+
import inspect
|
9
|
+
import sys
|
10
|
+
|
11
|
+
from itertools import chain
|
12
|
+
|
13
|
+
# deprecated: description='typing.Self exists in Python 3.11+' python_version='3.10'
|
14
|
+
from ansible.module_utils.compat import typing as t
|
15
|
+
|
16
|
+
from ansible.module_utils._internal import _dataclass_validation
|
17
|
+
from ansible.module_utils._internal._patches import _sys_intern_patch, _socket_patch
|
18
|
+
|
19
|
+
_sys_intern_patch.SysInternPatch.patch()
|
20
|
+
_socket_patch.GetAddrInfoPatch.patch() # DTFIX-FUTURE: consider replacing this with a socket import shim that installs the patch
|
21
|
+
|
22
|
+
if sys.version_info >= (3, 10):
|
23
|
+
# Using slots for reduced memory usage and improved performance.
|
24
|
+
_tag_dataclass_kwargs = dict(frozen=True, repr=False, kw_only=True, slots=True)
|
25
|
+
else:
|
26
|
+
# deprecated: description='always use dataclass slots and keyword-only args' python_version='3.9'
|
27
|
+
_tag_dataclass_kwargs = dict(frozen=True, repr=False)
|
28
|
+
|
29
|
+
_T = t.TypeVar('_T')
|
30
|
+
_TAnsibleSerializable = t.TypeVar('_TAnsibleSerializable', bound='AnsibleSerializable')
|
31
|
+
_TAnsibleDatatagBase = t.TypeVar('_TAnsibleDatatagBase', bound='AnsibleDatatagBase')
|
32
|
+
_TAnsibleTaggedObject = t.TypeVar('_TAnsibleTaggedObject', bound='AnsibleTaggedObject')
|
33
|
+
|
34
|
+
_NO_INSTANCE_STORAGE = t.cast(t.Tuple[str], tuple())
|
35
|
+
_ANSIBLE_TAGGED_OBJECT_SLOTS = tuple(('_ansible_tags_mapping',))
|
36
|
+
|
37
|
+
# shared empty frozenset for default values
|
38
|
+
_empty_frozenset: t.FrozenSet = frozenset()
|
39
|
+
|
40
|
+
|
41
|
+
class AnsibleTagHelper:
|
42
|
+
"""Utility methods for working with Ansible data tags."""
|
43
|
+
|
44
|
+
# DTFIX-RELEASE: bikeshed the name and location of this class, also, related, how much more of it should be exposed as public API?
|
45
|
+
# it may make sense to move this into another module, but the implementations should remain here (so they can be used without circular imports here)
|
46
|
+
# if they're in a separate module, is a class even needed, or should they be globals?
|
47
|
+
# DTFIX-RELEASE: add docstrings to all non-override methods in this class
|
48
|
+
|
49
|
+
@staticmethod
|
50
|
+
def untag(value: _T, *tag_types: t.Type[AnsibleDatatagBase]) -> _T:
|
51
|
+
"""
|
52
|
+
If tags matching any of `tag_types` are present on `value`, return a copy with those tags removed.
|
53
|
+
If no `tag_types` are specified and the object has tags, return a copy with all tags removed.
|
54
|
+
Otherwise, the original `value` is returned.
|
55
|
+
"""
|
56
|
+
tag_set = AnsibleTagHelper.tags(value)
|
57
|
+
|
58
|
+
if not tag_set:
|
59
|
+
return value
|
60
|
+
|
61
|
+
if tag_types:
|
62
|
+
tags_mapping = _AnsibleTagsMapping((type(tag), tag) for tag in tag_set if type(tag) not in tag_types) # pylint: disable=unidiomatic-typecheck
|
63
|
+
|
64
|
+
if len(tags_mapping) == len(tag_set):
|
65
|
+
return value # if no tags were removed, return the original instance
|
66
|
+
else:
|
67
|
+
tags_mapping = None
|
68
|
+
|
69
|
+
if not tags_mapping:
|
70
|
+
if t.cast(AnsibleTaggedObject, value)._empty_tags_as_native:
|
71
|
+
return t.cast(AnsibleTaggedObject, value)._native_copy()
|
72
|
+
|
73
|
+
tags_mapping = _EMPTY_INTERNAL_TAGS_MAPPING
|
74
|
+
|
75
|
+
tagged_type = AnsibleTaggedObject._get_tagged_type(type(value))
|
76
|
+
|
77
|
+
return t.cast(_T, tagged_type._instance_factory(value, tags_mapping))
|
78
|
+
|
79
|
+
@staticmethod
|
80
|
+
def tags(value: t.Any) -> t.FrozenSet[AnsibleDatatagBase]:
|
81
|
+
tags = _try_get_internal_tags_mapping(value)
|
82
|
+
|
83
|
+
if tags is _EMPTY_INTERNAL_TAGS_MAPPING:
|
84
|
+
return _empty_frozenset
|
85
|
+
|
86
|
+
return frozenset(tags.values())
|
87
|
+
|
88
|
+
@staticmethod
|
89
|
+
def tag_types(value: t.Any) -> t.FrozenSet[t.Type[AnsibleDatatagBase]]:
|
90
|
+
tags = _try_get_internal_tags_mapping(value)
|
91
|
+
|
92
|
+
if tags is _EMPTY_INTERNAL_TAGS_MAPPING:
|
93
|
+
return _empty_frozenset
|
94
|
+
|
95
|
+
return frozenset(tags)
|
96
|
+
|
97
|
+
@staticmethod
|
98
|
+
def base_type(type_or_value: t.Any, /) -> type:
|
99
|
+
"""Return the friendly type of the given type or value. If the type is an AnsibleTaggedObject, the native type will be used."""
|
100
|
+
if isinstance(type_or_value, type):
|
101
|
+
the_type = type_or_value
|
102
|
+
else:
|
103
|
+
the_type = type(type_or_value)
|
104
|
+
|
105
|
+
if issubclass(the_type, AnsibleTaggedObject):
|
106
|
+
the_type = type_or_value._native_type
|
107
|
+
|
108
|
+
# DTFIX-RELEASE: provide a way to report the real type for debugging purposes
|
109
|
+
return the_type
|
110
|
+
|
111
|
+
@staticmethod
|
112
|
+
def as_native_type(value: _T) -> _T:
|
113
|
+
"""
|
114
|
+
Returns an untagged native data type matching the input value, or the original input if the value was not a tagged type.
|
115
|
+
Containers are not recursively processed.
|
116
|
+
"""
|
117
|
+
if isinstance(value, AnsibleTaggedObject):
|
118
|
+
value = value._native_copy()
|
119
|
+
|
120
|
+
return value
|
121
|
+
|
122
|
+
@staticmethod
|
123
|
+
@t.overload
|
124
|
+
def tag_copy(src: t.Any, value: _T) -> _T: ... # pragma: nocover
|
125
|
+
|
126
|
+
@staticmethod
|
127
|
+
@t.overload
|
128
|
+
def tag_copy(src: t.Any, value: t.Any, *, value_type: type[_T]) -> _T: ... # pragma: nocover
|
129
|
+
|
130
|
+
@staticmethod
|
131
|
+
@t.overload
|
132
|
+
def tag_copy(src: t.Any, value: _T, *, value_type: None = None) -> _T: ... # pragma: nocover
|
133
|
+
|
134
|
+
@staticmethod
|
135
|
+
def tag_copy(src: t.Any, value: _T, *, value_type: t.Optional[type] = None) -> _T:
|
136
|
+
"""Return a copy of `value`, with tags copied from `src`, overwriting any existing tags of the same types."""
|
137
|
+
src_tags = AnsibleTagHelper.tags(src)
|
138
|
+
value_tags = [(tag, tag._get_tag_to_propagate(src, value, value_type=value_type)) for tag in src_tags]
|
139
|
+
tags = [tag[1] for tag in value_tags if tag[1] is not None]
|
140
|
+
tag_types_to_remove = [type(tag[0]) for tag in value_tags if tag[1] is None]
|
141
|
+
|
142
|
+
if tag_types_to_remove:
|
143
|
+
value = AnsibleTagHelper.untag(value, *tag_types_to_remove)
|
144
|
+
|
145
|
+
return AnsibleTagHelper.tag(value, tags, value_type=value_type)
|
146
|
+
|
147
|
+
@staticmethod
|
148
|
+
@t.overload
|
149
|
+
def tag(value: _T, tags: t.Union[AnsibleDatatagBase, t.Iterable[AnsibleDatatagBase]]) -> _T: ... # pragma: nocover
|
150
|
+
|
151
|
+
@staticmethod
|
152
|
+
@t.overload
|
153
|
+
def tag(value: t.Any, tags: t.Union[AnsibleDatatagBase, t.Iterable[AnsibleDatatagBase]], *, value_type: type[_T]) -> _T: ... # pragma: nocover
|
154
|
+
|
155
|
+
@staticmethod
|
156
|
+
@t.overload
|
157
|
+
def tag(value: _T, tags: t.Union[AnsibleDatatagBase, t.Iterable[AnsibleDatatagBase]], *, value_type: None = None) -> _T: ... # pragma: nocover
|
158
|
+
|
159
|
+
@staticmethod
|
160
|
+
def tag(value: _T, tags: t.Union[AnsibleDatatagBase, t.Iterable[AnsibleDatatagBase]], *, value_type: t.Optional[type] = None) -> _T:
|
161
|
+
"""
|
162
|
+
Return a copy of `value`, with `tags` applied, overwriting any existing tags of the same types.
|
163
|
+
If `value` is an ignored type, or `tags` is empty, the original `value` will be returned.
|
164
|
+
If `value` is not taggable, a `NotTaggableError` exception will be raised.
|
165
|
+
If `value_type` was given, that type will be returned instead.
|
166
|
+
"""
|
167
|
+
if value_type is None:
|
168
|
+
value_type_specified = False
|
169
|
+
value_type = type(value)
|
170
|
+
else:
|
171
|
+
value_type_specified = True
|
172
|
+
|
173
|
+
# if no tags to apply, just return what we got
|
174
|
+
# NB: this only works because the untaggable types are singletons (and thus direct type comparison works)
|
175
|
+
if not tags or value_type in _untaggable_types:
|
176
|
+
if value_type_specified:
|
177
|
+
return value_type(value)
|
178
|
+
|
179
|
+
return value
|
180
|
+
|
181
|
+
tag_list: list[AnsibleDatatagBase]
|
182
|
+
|
183
|
+
# noinspection PyProtectedMember
|
184
|
+
if type(tags) in _known_tag_types:
|
185
|
+
tag_list = [tags] # type: ignore[list-item]
|
186
|
+
else:
|
187
|
+
tag_list = list(tags) # type: ignore[arg-type]
|
188
|
+
|
189
|
+
for idx, tag in enumerate(tag_list):
|
190
|
+
# noinspection PyProtectedMember
|
191
|
+
if type(tag) not in _known_tag_types:
|
192
|
+
# noinspection PyProtectedMember
|
193
|
+
raise TypeError(f'tags[{idx}] of type {type(tag)} is not one of {_known_tag_types}')
|
194
|
+
|
195
|
+
existing_internal_tags_mapping = _try_get_internal_tags_mapping(value)
|
196
|
+
|
197
|
+
if existing_internal_tags_mapping is not _EMPTY_INTERNAL_TAGS_MAPPING:
|
198
|
+
# include the existing tags first so new tags of the same type will overwrite
|
199
|
+
tag_list = list(chain(existing_internal_tags_mapping.values(), tag_list))
|
200
|
+
|
201
|
+
tags_mapping = _AnsibleTagsMapping((type(tag), tag) for tag in tag_list)
|
202
|
+
tagged_type = AnsibleTaggedObject._get_tagged_type(value_type)
|
203
|
+
|
204
|
+
return t.cast(_T, tagged_type._instance_factory(value, tags_mapping))
|
205
|
+
|
206
|
+
@staticmethod
|
207
|
+
def try_tag(value: _T, tags: t.Union[AnsibleDatatagBase, t.Iterable[AnsibleDatatagBase]]) -> _T:
|
208
|
+
"""
|
209
|
+
Return a copy of `value`, with `tags` applied, overwriting any existing tags of the same types.
|
210
|
+
If `value` is not taggable or `tags` is empty, the original `value` will be returned.
|
211
|
+
"""
|
212
|
+
try:
|
213
|
+
return AnsibleTagHelper.tag(value, tags)
|
214
|
+
except NotTaggableError:
|
215
|
+
return value
|
216
|
+
|
217
|
+
|
218
|
+
class AnsibleSerializable(metaclass=abc.ABCMeta):
|
219
|
+
__slots__ = _NO_INSTANCE_STORAGE
|
220
|
+
|
221
|
+
_known_type_map: t.ClassVar[t.Dict[str, t.Type['AnsibleSerializable']]] = {}
|
222
|
+
_TYPE_KEY: t.ClassVar[str] = '__ansible_type'
|
223
|
+
|
224
|
+
_type_key: t.ClassVar[str]
|
225
|
+
|
226
|
+
def __init_subclass__(cls, **kwargs) -> None:
|
227
|
+
# this is needed to call __init__subclass__ on mixins for derived types
|
228
|
+
super().__init_subclass__(**kwargs)
|
229
|
+
|
230
|
+
cls._type_key = cls.__name__
|
231
|
+
|
232
|
+
# DTFIX-FUTURE: is there a better way to exclude non-abstract types which are base classes?
|
233
|
+
if not inspect.isabstract(cls) and not cls.__name__.endswith('Base') and cls.__name__ != 'AnsibleTaggedObject':
|
234
|
+
AnsibleSerializable._known_type_map[cls._type_key] = cls
|
235
|
+
|
236
|
+
@classmethod
|
237
|
+
@abc.abstractmethod
|
238
|
+
def _from_dict(cls: t.Type[_TAnsibleSerializable], d: t.Dict[str, t.Any]) -> object:
|
239
|
+
"""Return an instance of this type, created from the given dictionary."""
|
240
|
+
|
241
|
+
@abc.abstractmethod
|
242
|
+
def _as_dict(self) -> t.Dict[str, t.Any]:
|
243
|
+
"""
|
244
|
+
Return a serialized version of this instance as a dictionary.
|
245
|
+
This operation is *NOT* recursive - the returned dictionary may still include custom types.
|
246
|
+
It is the responsibility of the caller to handle recursion of the returned dict.
|
247
|
+
"""
|
248
|
+
|
249
|
+
def _serialize(self) -> t.Dict[str, t.Any]:
|
250
|
+
value = self._as_dict()
|
251
|
+
value.update({AnsibleSerializable._TYPE_KEY: self._type_key})
|
252
|
+
|
253
|
+
return value
|
254
|
+
|
255
|
+
@staticmethod
|
256
|
+
def _deserialize(data: t.Dict[str, t.Any]) -> object:
|
257
|
+
"""Deserialize an object from the supplied data dict, which will be mutated if it contains a type key."""
|
258
|
+
type_name = data.pop(AnsibleSerializable._TYPE_KEY, ...) # common usage assumes `data` is an intermediate dict provided by a deserializer
|
259
|
+
|
260
|
+
if type_name is ...:
|
261
|
+
return None
|
262
|
+
|
263
|
+
type_value = AnsibleSerializable._known_type_map.get(type_name)
|
264
|
+
|
265
|
+
if not type_value:
|
266
|
+
raise ValueError(f'An unknown {AnsibleSerializable._TYPE_KEY!r} value {type_name!r} was encountered during deserialization.')
|
267
|
+
|
268
|
+
return type_value._from_dict(data)
|
269
|
+
|
270
|
+
def _repr(self, name: str) -> str:
|
271
|
+
args = self._as_dict()
|
272
|
+
arg_string = ', '.join((f'{k}={v!r}' for k, v in args.items()))
|
273
|
+
return f'{name}({arg_string})'
|
274
|
+
|
275
|
+
|
276
|
+
class AnsibleSerializableWrapper(AnsibleSerializable, t.Generic[_T], metaclass=abc.ABCMeta):
|
277
|
+
__slots__ = ('_value',)
|
278
|
+
|
279
|
+
_wrapped_types: t.ClassVar[dict[type, type[AnsibleSerializable]]] = {}
|
280
|
+
_wrapped_type: t.ClassVar[type] = type(None)
|
281
|
+
|
282
|
+
def __init__(self, value: _T) -> None:
|
283
|
+
self._value: _T = value
|
284
|
+
|
285
|
+
def __init_subclass__(cls, **kwargs):
|
286
|
+
super().__init_subclass__(**kwargs)
|
287
|
+
|
288
|
+
cls._wrapped_type = t.get_args(cls.__orig_bases__[0])[0]
|
289
|
+
cls._wrapped_types[cls._wrapped_type] = cls
|
290
|
+
|
291
|
+
|
292
|
+
class AnsibleSerializableDate(AnsibleSerializableWrapper[datetime.date]):
|
293
|
+
__slots__ = _NO_INSTANCE_STORAGE
|
294
|
+
|
295
|
+
@classmethod
|
296
|
+
def _from_dict(cls: t.Type[_TAnsibleSerializable], d: t.Dict[str, t.Any]) -> datetime.date:
|
297
|
+
return datetime.date.fromisoformat(d['iso8601'])
|
298
|
+
|
299
|
+
def _as_dict(self) -> t.Dict[str, t.Any]:
|
300
|
+
return dict(
|
301
|
+
iso8601=self._value.isoformat(),
|
302
|
+
)
|
303
|
+
|
304
|
+
|
305
|
+
class AnsibleSerializableTime(AnsibleSerializableWrapper[datetime.time]):
|
306
|
+
__slots__ = _NO_INSTANCE_STORAGE
|
307
|
+
|
308
|
+
@classmethod
|
309
|
+
def _from_dict(cls: t.Type[_TAnsibleSerializable], d: t.Dict[str, t.Any]) -> datetime.time:
|
310
|
+
value = datetime.time.fromisoformat(d['iso8601'])
|
311
|
+
value.replace(fold=d['fold'])
|
312
|
+
|
313
|
+
return value
|
314
|
+
|
315
|
+
def _as_dict(self) -> t.Dict[str, t.Any]:
|
316
|
+
return dict(
|
317
|
+
iso8601=self._value.isoformat(),
|
318
|
+
fold=self._value.fold,
|
319
|
+
)
|
320
|
+
|
321
|
+
|
322
|
+
class AnsibleSerializableDateTime(AnsibleSerializableWrapper[datetime.datetime]):
|
323
|
+
__slots__ = _NO_INSTANCE_STORAGE
|
324
|
+
|
325
|
+
@classmethod
|
326
|
+
def _from_dict(cls: t.Type[_TAnsibleSerializable], d: t.Dict[str, t.Any]) -> datetime.datetime:
|
327
|
+
value = datetime.datetime.fromisoformat(d['iso8601'])
|
328
|
+
value.replace(fold=d['fold'])
|
329
|
+
|
330
|
+
return value
|
331
|
+
|
332
|
+
def _as_dict(self) -> t.Dict[str, t.Any]:
|
333
|
+
return dict(
|
334
|
+
iso8601=self._value.isoformat(),
|
335
|
+
fold=self._value.fold,
|
336
|
+
)
|
337
|
+
|
338
|
+
|
339
|
+
@dataclasses.dataclass(**_tag_dataclass_kwargs)
|
340
|
+
class AnsibleSerializableDataclass(AnsibleSerializable, metaclass=abc.ABCMeta):
|
341
|
+
_validation_allow_subclasses = True
|
342
|
+
|
343
|
+
def _as_dict(self) -> t.Dict[str, t.Any]:
|
344
|
+
# omit None values when None is the field default
|
345
|
+
# DTFIX-RELEASE: this implementation means we can never change the default on fields which have None for their default
|
346
|
+
# other defaults can be changed -- but there's no way to override this behavior either way for other default types
|
347
|
+
# it's a trip hazard to have the default logic here, rather than per field (or not at all)
|
348
|
+
# consider either removing the filtering or requiring it to be explicitly set per field using dataclass metadata
|
349
|
+
fields = ((field, getattr(self, field.name)) for field in dataclasses.fields(self))
|
350
|
+
return {field.name: value for field, value in fields if value is not None or field.default is not None}
|
351
|
+
|
352
|
+
@classmethod
|
353
|
+
def _from_dict(cls, d: t.Dict[str, t.Any]) -> t.Self:
|
354
|
+
# DTFIX-RELEASE: optimize this to avoid the dataclasses fields metadata and get_origin stuff at runtime
|
355
|
+
type_hints = t.get_type_hints(cls)
|
356
|
+
mutated_dict: dict[str, t.Any] | None = None
|
357
|
+
|
358
|
+
for field in dataclasses.fields(cls):
|
359
|
+
if t.get_origin(type_hints[field.name]) is tuple: # NOTE: only supports bare tuples, not optional or inside a union
|
360
|
+
if type(field_value := d.get(field.name)) is list: # pylint: disable=unidiomatic-typecheck
|
361
|
+
if mutated_dict is None:
|
362
|
+
mutated_dict = d.copy()
|
363
|
+
|
364
|
+
mutated_dict[field.name] = tuple(field_value)
|
365
|
+
|
366
|
+
return cls(**(mutated_dict or d))
|
367
|
+
|
368
|
+
def __init_subclass__(cls, **kwargs) -> None:
|
369
|
+
super(AnsibleSerializableDataclass, cls).__init_subclass__(**kwargs) # cannot use super() without arguments when using slots
|
370
|
+
|
371
|
+
_dataclass_validation.inject_post_init_validation(cls, cls._validation_allow_subclasses) # code gen a real __post_init__ method
|
372
|
+
|
373
|
+
|
374
|
+
class Tripwire:
|
375
|
+
"""Marker mixin for types that should raise an error when encountered."""
|
376
|
+
|
377
|
+
__slots__ = _NO_INSTANCE_STORAGE
|
378
|
+
|
379
|
+
def trip(self) -> t.NoReturn:
|
380
|
+
"""Derived types should implement a failure behavior."""
|
381
|
+
raise NotImplementedError()
|
382
|
+
|
383
|
+
|
384
|
+
@dataclasses.dataclass(**_tag_dataclass_kwargs)
|
385
|
+
class AnsibleDatatagBase(AnsibleSerializableDataclass, metaclass=abc.ABCMeta):
|
386
|
+
"""
|
387
|
+
Base class for data tagging tag types.
|
388
|
+
New tag types need to be considered very carefully; e.g.: which serialization/runtime contexts they're allowed in, fallback behavior, propagation.
|
389
|
+
"""
|
390
|
+
|
391
|
+
_validation_allow_subclasses = False
|
392
|
+
|
393
|
+
def __init_subclass__(cls, **kwargs) -> None:
|
394
|
+
# NOTE: This method is called twice when the datatag type is a dataclass.
|
395
|
+
super(AnsibleDatatagBase, cls).__init_subclass__(**kwargs) # cannot use super() without arguments when using slots
|
396
|
+
|
397
|
+
# DTFIX-FUTURE: "freeze" this after module init has completed to discourage custom external tag subclasses
|
398
|
+
|
399
|
+
# DTFIX-FUTURE: is there a better way to exclude non-abstract types which are base classes?
|
400
|
+
if not inspect.isabstract(cls) and not cls.__name__.endswith('Base'):
|
401
|
+
existing = _known_tag_type_map.get(cls.__name__)
|
402
|
+
|
403
|
+
if existing:
|
404
|
+
# When the datatag type is a dataclass, the first instance will be the non-dataclass type.
|
405
|
+
# It must be removed from the known tag types before adding the dataclass version.
|
406
|
+
_known_tag_types.remove(existing)
|
407
|
+
|
408
|
+
_known_tag_type_map[cls.__name__] = cls
|
409
|
+
_known_tag_types.add(cls)
|
410
|
+
|
411
|
+
@classmethod
|
412
|
+
def is_tagged_on(cls, value: t.Any) -> bool:
|
413
|
+
return cls in _try_get_internal_tags_mapping(value)
|
414
|
+
|
415
|
+
@classmethod
|
416
|
+
def first_tagged_on(cls, *values: t.Any) -> t.Any | None:
|
417
|
+
"""Return the first value which is tagged with this type, or None if no match is found."""
|
418
|
+
for value in values:
|
419
|
+
if cls.is_tagged_on(value):
|
420
|
+
return value
|
421
|
+
|
422
|
+
return None
|
423
|
+
|
424
|
+
@classmethod
|
425
|
+
def get_tag(cls, value: t.Any) -> t.Optional[t.Self]:
|
426
|
+
return _try_get_internal_tags_mapping(value).get(cls)
|
427
|
+
|
428
|
+
@classmethod
|
429
|
+
def get_required_tag(cls, value: t.Any) -> t.Self:
|
430
|
+
if (tag := cls.get_tag(value)) is None:
|
431
|
+
# DTFIX-FUTURE: we really should have a way to use AnsibleError with obj in module_utils when it's controller-side
|
432
|
+
raise ValueError(f'The type {type(value).__name__!r} is not tagged with {cls.__name__!r}.')
|
433
|
+
|
434
|
+
return tag
|
435
|
+
|
436
|
+
@classmethod
|
437
|
+
def untag(cls, value: _T) -> _T:
|
438
|
+
"""
|
439
|
+
If this tag type is present on `value`, return a copy with that tag removed.
|
440
|
+
Otherwise, the original `value` is returned.
|
441
|
+
"""
|
442
|
+
return AnsibleTagHelper.untag(value, cls)
|
443
|
+
|
444
|
+
def tag(self, value: _T) -> _T:
|
445
|
+
"""
|
446
|
+
Return a copy of `value` with this tag applied, overwriting any existing tag of the same type.
|
447
|
+
If `value` is an ignored type, the original `value` will be returned.
|
448
|
+
If `value` is not taggable, a `NotTaggableError` exception will be raised.
|
449
|
+
"""
|
450
|
+
return AnsibleTagHelper.tag(value, self)
|
451
|
+
|
452
|
+
def try_tag(self, value: _T) -> _T:
|
453
|
+
"""
|
454
|
+
Return a copy of `value` with this tag applied, overwriting any existing tag of the same type.
|
455
|
+
If `value` is not taggable, the original `value` will be returned.
|
456
|
+
"""
|
457
|
+
return AnsibleTagHelper.try_tag(value, self)
|
458
|
+
|
459
|
+
def _get_tag_to_propagate(self, src: t.Any, value: object, *, value_type: t.Optional[type] = None) -> t.Self | None:
|
460
|
+
"""
|
461
|
+
Called by `AnsibleTagHelper.tag_copy` during tag propagation.
|
462
|
+
Returns an instance of this tag appropriate for propagation to `value`, or `None` if the tag should not be propagated.
|
463
|
+
Derived implementations may consult the arguments relayed from `tag_copy` to determine if and how the tag should be propagated.
|
464
|
+
"""
|
465
|
+
return self
|
466
|
+
|
467
|
+
def __repr__(self) -> str:
|
468
|
+
return AnsibleSerializable._repr(self, self.__class__.__name__)
|
469
|
+
|
470
|
+
|
471
|
+
# used by the datatag Ansible/Jinja test plugin to find tags by name
|
472
|
+
_known_tag_type_map: t.Dict[str, t.Type[AnsibleDatatagBase]] = {}
|
473
|
+
_known_tag_types: t.Set[t.Type[AnsibleDatatagBase]] = set()
|
474
|
+
|
475
|
+
if sys.version_info >= (3, 9):
|
476
|
+
# Include the key and value types in the type hints on Python 3.9 and later.
|
477
|
+
# Earlier versions do not support subscriptable dict.
|
478
|
+
# deprecated: description='always use subscriptable dict' python_version='3.8'
|
479
|
+
class _AnsibleTagsMapping(dict[type[_TAnsibleDatatagBase], _TAnsibleDatatagBase]):
|
480
|
+
__slots__ = _NO_INSTANCE_STORAGE
|
481
|
+
|
482
|
+
else:
|
483
|
+
|
484
|
+
class _AnsibleTagsMapping(dict):
|
485
|
+
__slots__ = _NO_INSTANCE_STORAGE
|
486
|
+
|
487
|
+
|
488
|
+
class _EmptyROInternalTagsMapping(dict):
|
489
|
+
"""
|
490
|
+
Optimizes empty tag mapping by using a shared singleton read-only dict.
|
491
|
+
Since mappingproxy is not pickle-able and causes other problems, we had to roll our own.
|
492
|
+
"""
|
493
|
+
|
494
|
+
def __new__(cls):
|
495
|
+
try:
|
496
|
+
# noinspection PyUnresolvedReferences
|
497
|
+
return cls._instance
|
498
|
+
except AttributeError:
|
499
|
+
cls._instance = dict.__new__(cls)
|
500
|
+
|
501
|
+
# noinspection PyUnresolvedReferences
|
502
|
+
return cls._instance
|
503
|
+
|
504
|
+
def __setitem__(self, key, value):
|
505
|
+
raise NotImplementedError()
|
506
|
+
|
507
|
+
def setdefault(self, __key, __default=None):
|
508
|
+
raise NotImplementedError()
|
509
|
+
|
510
|
+
def update(self, __m, **kwargs):
|
511
|
+
raise NotImplementedError()
|
512
|
+
|
513
|
+
|
514
|
+
_EMPTY_INTERNAL_TAGS_MAPPING = t.cast(_AnsibleTagsMapping, _EmptyROInternalTagsMapping())
|
515
|
+
"""
|
516
|
+
An empty read-only mapping of tags.
|
517
|
+
Also used as a sentinel to cheaply determine that a type is not tagged by using a reference equality check.
|
518
|
+
"""
|
519
|
+
|
520
|
+
|
521
|
+
class CollectionWithMro(c.Collection, t.Protocol):
|
522
|
+
"""Used to represent a Collection with __mro__ in a TypeGuard for tools that don't include __mro__ in Collection."""
|
523
|
+
|
524
|
+
__mro__: tuple[type, ...]
|
525
|
+
|
526
|
+
|
527
|
+
# DTFIX-RELEASE: This should probably reside elsewhere.
|
528
|
+
def is_non_scalar_collection_type(value: type) -> t.TypeGuard[type[CollectionWithMro]]:
|
529
|
+
"""Returns True if the value is a non-scalar collection type, otherwise returns False."""
|
530
|
+
return issubclass(value, c.Collection) and not issubclass(value, str) and not issubclass(value, bytes)
|
531
|
+
|
532
|
+
|
533
|
+
def _try_get_internal_tags_mapping(value: t.Any) -> _AnsibleTagsMapping:
|
534
|
+
"""Return the internal tag mapping of the given value, or a sentinel value if it is not tagged."""
|
535
|
+
# noinspection PyBroadException
|
536
|
+
try:
|
537
|
+
# noinspection PyProtectedMember
|
538
|
+
tags = value._ansible_tags_mapping
|
539
|
+
except Exception:
|
540
|
+
# try/except is a cheap way to determine if this is a tagged object without using isinstance
|
541
|
+
# handling Exception accounts for types that may raise something other than AttributeError
|
542
|
+
return _EMPTY_INTERNAL_TAGS_MAPPING
|
543
|
+
|
544
|
+
# handle cases where the instance always returns something, such as Marker or MagicMock
|
545
|
+
if type(tags) is not _AnsibleTagsMapping: # pylint: disable=unidiomatic-typecheck
|
546
|
+
return _EMPTY_INTERNAL_TAGS_MAPPING
|
547
|
+
|
548
|
+
return tags
|
549
|
+
|
550
|
+
|
551
|
+
class NotTaggableError(TypeError):
|
552
|
+
def __init__(self, value):
|
553
|
+
super(NotTaggableError, self).__init__('{} is not taggable'.format(value))
|
554
|
+
|
555
|
+
|
556
|
+
@dataclasses.dataclass(**_tag_dataclass_kwargs)
|
557
|
+
class AnsibleSingletonTagBase(AnsibleDatatagBase):
|
558
|
+
def __new__(cls):
|
559
|
+
try:
|
560
|
+
# noinspection PyUnresolvedReferences
|
561
|
+
return cls._instance
|
562
|
+
except AttributeError:
|
563
|
+
cls._instance = AnsibleDatatagBase.__new__(cls)
|
564
|
+
|
565
|
+
# noinspection PyUnresolvedReferences
|
566
|
+
return cls._instance
|
567
|
+
|
568
|
+
def _as_dict(self) -> t.Dict[str, t.Any]:
|
569
|
+
return {}
|
570
|
+
|
571
|
+
|
572
|
+
class AnsibleTaggedObject(AnsibleSerializable):
|
573
|
+
__slots__ = _NO_INSTANCE_STORAGE
|
574
|
+
|
575
|
+
_native_type: t.ClassVar[type]
|
576
|
+
_item_source: t.ClassVar[t.Optional[t.Callable]] = None
|
577
|
+
|
578
|
+
_tagged_type_map: t.ClassVar[t.Dict[type, t.Type['AnsibleTaggedObject']]] = {}
|
579
|
+
_tagged_collection_types: t.ClassVar[t.Set[t.Type[c.Collection]]] = set()
|
580
|
+
_collection_types: t.ClassVar[t.Set[t.Type[c.Collection]]] = set()
|
581
|
+
|
582
|
+
_empty_tags_as_native: t.ClassVar[bool] = True # by default, untag will revert to the native type when no tags remain
|
583
|
+
_subclasses_native_type: t.ClassVar[bool] = True # by default, tagged types are assumed to subclass the type they augment
|
584
|
+
|
585
|
+
_ansible_tags_mapping: _AnsibleTagsMapping | _EmptyROInternalTagsMapping = _EMPTY_INTERNAL_TAGS_MAPPING
|
586
|
+
"""
|
587
|
+
Efficient internal storage of tags, indexed by tag type.
|
588
|
+
Contains no more than one instance of each tag type.
|
589
|
+
This is defined as a class attribute to support type hinting and documentation.
|
590
|
+
It is overwritten with an instance attribute during instance creation.
|
591
|
+
The instance attribute slot is provided by the derived type.
|
592
|
+
"""
|
593
|
+
|
594
|
+
def __init_subclass__(cls, **kwargs) -> None:
|
595
|
+
super().__init_subclass__(**kwargs)
|
596
|
+
|
597
|
+
try:
|
598
|
+
init_class = cls._init_class # type: ignore[attr-defined]
|
599
|
+
except AttributeError:
|
600
|
+
pass
|
601
|
+
else:
|
602
|
+
init_class()
|
603
|
+
|
604
|
+
if not cls._subclasses_native_type:
|
605
|
+
return # NOTE: When not subclassing a native type, the derived type must set cls._native_type itself and cls._empty_tags_as_native to False.
|
606
|
+
|
607
|
+
try:
|
608
|
+
# Subclasses of tagged types will already have a native type set and won't need to detect it.
|
609
|
+
# Special types which do not subclass a native type can also have their native type already set.
|
610
|
+
# Automatic item source selection is only implemented for types that don't set _native_type.
|
611
|
+
cls._native_type
|
612
|
+
except AttributeError:
|
613
|
+
# Direct subclasses of native types won't have cls._native_type set, so detect the native type.
|
614
|
+
cls._native_type = cls.__bases__[0]
|
615
|
+
|
616
|
+
# Detect the item source if not already set.
|
617
|
+
if cls._item_source is None and is_non_scalar_collection_type(cls._native_type):
|
618
|
+
cls._item_source = cls._native_type.__iter__ # type: ignore[attr-defined]
|
619
|
+
|
620
|
+
# Use a collection specific factory for types with item sources.
|
621
|
+
if cls._item_source:
|
622
|
+
cls._instance_factory = cls._instance_factory_collection # type: ignore[method-assign]
|
623
|
+
|
624
|
+
new_type_direct_subclass = cls.__mro__[1]
|
625
|
+
|
626
|
+
conflicting_impl = AnsibleTaggedObject._tagged_type_map.get(new_type_direct_subclass)
|
627
|
+
|
628
|
+
if conflicting_impl:
|
629
|
+
raise TypeError(f'Cannot define type {cls.__name__!r} since {conflicting_impl.__name__!r} already extends {new_type_direct_subclass.__name__!r}.')
|
630
|
+
|
631
|
+
AnsibleTaggedObject._tagged_type_map[new_type_direct_subclass] = cls
|
632
|
+
|
633
|
+
if is_non_scalar_collection_type(cls):
|
634
|
+
AnsibleTaggedObject._tagged_collection_types.add(cls)
|
635
|
+
AnsibleTaggedObject._collection_types.update({cls, new_type_direct_subclass})
|
636
|
+
|
637
|
+
def _native_copy(self) -> t.Any:
|
638
|
+
"""
|
639
|
+
Returns a copy of the current instance as its native Python type.
|
640
|
+
Any dynamic access behaviors that apply to this instance will be used during creation of the copy.
|
641
|
+
In the case of a container type, this is a shallow copy.
|
642
|
+
Recursive calls to native_copy are the responsibility of the caller.
|
643
|
+
"""
|
644
|
+
return self._native_type(self) # pylint: disable=abstract-class-instantiated
|
645
|
+
|
646
|
+
@classmethod
|
647
|
+
def _instance_factory(cls, value: t.Any, tags_mapping: _AnsibleTagsMapping) -> t.Self:
|
648
|
+
# There's no way to indicate cls is callable with a single arg without defining a useless __init__.
|
649
|
+
instance = cls(value) # type: ignore[call-arg]
|
650
|
+
instance._ansible_tags_mapping = tags_mapping
|
651
|
+
|
652
|
+
return instance
|
653
|
+
|
654
|
+
@staticmethod
|
655
|
+
def _get_tagged_type(value_type: type) -> type[AnsibleTaggedObject]:
|
656
|
+
tagged_type: t.Optional[type[AnsibleTaggedObject]]
|
657
|
+
|
658
|
+
if issubclass(value_type, AnsibleTaggedObject):
|
659
|
+
tagged_type = value_type
|
660
|
+
else:
|
661
|
+
tagged_type = AnsibleTaggedObject._tagged_type_map.get(value_type)
|
662
|
+
|
663
|
+
if not tagged_type:
|
664
|
+
raise NotTaggableError(value_type)
|
665
|
+
|
666
|
+
return tagged_type
|
667
|
+
|
668
|
+
def _as_dict(self) -> t.Dict[str, t.Any]:
|
669
|
+
return dict(
|
670
|
+
value=self._native_copy(),
|
671
|
+
tags=list(self._ansible_tags_mapping.values()),
|
672
|
+
)
|
673
|
+
|
674
|
+
@classmethod
|
675
|
+
def _from_dict(cls: t.Type[_TAnsibleTaggedObject], d: t.Dict[str, t.Any]) -> _TAnsibleTaggedObject:
|
676
|
+
return AnsibleTagHelper.tag(**d)
|
677
|
+
|
678
|
+
@classmethod
|
679
|
+
def _instance_factory_collection(
|
680
|
+
cls,
|
681
|
+
value: t.Any,
|
682
|
+
tags_mapping: _AnsibleTagsMapping,
|
683
|
+
) -> t.Self:
|
684
|
+
if type(value) in AnsibleTaggedObject._collection_types:
|
685
|
+
# use the underlying iterator to avoid access/iteration side effects (e.g. templating/wrapping on Lazy subclasses)
|
686
|
+
instance = cls(cls._item_source(value)) # type: ignore[call-arg,misc]
|
687
|
+
else:
|
688
|
+
# this is used when the value is a generator
|
689
|
+
instance = cls(value) # type: ignore[call-arg]
|
690
|
+
|
691
|
+
instance._ansible_tags_mapping = tags_mapping
|
692
|
+
|
693
|
+
return instance
|
694
|
+
|
695
|
+
def _copy_collection(self) -> AnsibleTaggedObject:
|
696
|
+
"""
|
697
|
+
Return a shallow copy of this instance, which must be a collection.
|
698
|
+
This uses the underlying iterator to avoid access/iteration side effects (e.g. templating/wrapping on Lazy subclasses).
|
699
|
+
"""
|
700
|
+
return AnsibleTagHelper.tag_copy(self, type(self)._item_source(self), value_type=type(self)) # type: ignore[misc]
|
701
|
+
|
702
|
+
@classmethod
|
703
|
+
def _new(cls, value: t.Any, *args, **kwargs) -> t.Self:
|
704
|
+
if type(value) is _AnsibleTagsMapping: # pylint: disable=unidiomatic-typecheck
|
705
|
+
self = cls._native_type.__new__(cls, *args, **kwargs)
|
706
|
+
self._ansible_tags_mapping = value
|
707
|
+
return self
|
708
|
+
|
709
|
+
return cls._native_type.__new__(cls, value, *args, **kwargs)
|
710
|
+
|
711
|
+
def _reduce(self, reduced: t.Union[str, tuple[t.Any, ...]]) -> tuple:
|
712
|
+
if type(reduced) is not tuple: # pylint: disable=unidiomatic-typecheck
|
713
|
+
raise TypeError()
|
714
|
+
|
715
|
+
updated: list[t.Any] = list(reduced)
|
716
|
+
updated[1] = (self._ansible_tags_mapping,) + updated[1]
|
717
|
+
|
718
|
+
return tuple(updated)
|
719
|
+
|
720
|
+
|
721
|
+
class _AnsibleTaggedStr(str, AnsibleTaggedObject):
|
722
|
+
__slots__ = _ANSIBLE_TAGGED_OBJECT_SLOTS
|
723
|
+
|
724
|
+
|
725
|
+
class _AnsibleTaggedBytes(bytes, AnsibleTaggedObject):
|
726
|
+
# nonempty __slots__ not supported for subtype of 'bytes'
|
727
|
+
pass
|
728
|
+
|
729
|
+
|
730
|
+
class _AnsibleTaggedInt(int, AnsibleTaggedObject):
|
731
|
+
# nonempty __slots__ not supported for subtype of 'int'
|
732
|
+
pass
|
733
|
+
|
734
|
+
|
735
|
+
class _AnsibleTaggedFloat(float, AnsibleTaggedObject):
|
736
|
+
__slots__ = _ANSIBLE_TAGGED_OBJECT_SLOTS
|
737
|
+
|
738
|
+
|
739
|
+
class _AnsibleTaggedDateTime(datetime.datetime, AnsibleTaggedObject):
|
740
|
+
__slots__ = _ANSIBLE_TAGGED_OBJECT_SLOTS
|
741
|
+
|
742
|
+
@classmethod
|
743
|
+
def _instance_factory(cls, value: datetime.datetime, tags_mapping: _AnsibleTagsMapping) -> _AnsibleTaggedDateTime:
|
744
|
+
instance = cls(
|
745
|
+
year=value.year,
|
746
|
+
month=value.month,
|
747
|
+
day=value.day,
|
748
|
+
hour=value.hour,
|
749
|
+
minute=value.minute,
|
750
|
+
second=value.second,
|
751
|
+
microsecond=value.microsecond,
|
752
|
+
tzinfo=value.tzinfo,
|
753
|
+
fold=value.fold,
|
754
|
+
)
|
755
|
+
|
756
|
+
instance._ansible_tags_mapping = tags_mapping
|
757
|
+
|
758
|
+
return instance
|
759
|
+
|
760
|
+
def _native_copy(self) -> datetime.datetime:
|
761
|
+
return datetime.datetime(
|
762
|
+
year=self.year,
|
763
|
+
month=self.month,
|
764
|
+
day=self.day,
|
765
|
+
hour=self.hour,
|
766
|
+
minute=self.minute,
|
767
|
+
second=self.second,
|
768
|
+
microsecond=self.microsecond,
|
769
|
+
tzinfo=self.tzinfo,
|
770
|
+
fold=self.fold,
|
771
|
+
)
|
772
|
+
|
773
|
+
def __new__(cls, year, *args, **kwargs):
|
774
|
+
return super()._new(year, *args, **kwargs)
|
775
|
+
|
776
|
+
def __reduce_ex__(self, protocol: t.SupportsIndex) -> tuple:
|
777
|
+
return super()._reduce(super().__reduce_ex__(protocol))
|
778
|
+
|
779
|
+
def __repr__(self) -> str:
|
780
|
+
return self._native_copy().__repr__()
|
781
|
+
|
782
|
+
|
783
|
+
class _AnsibleTaggedDate(datetime.date, AnsibleTaggedObject):
|
784
|
+
__slots__ = _ANSIBLE_TAGGED_OBJECT_SLOTS
|
785
|
+
|
786
|
+
@classmethod
|
787
|
+
def _instance_factory(cls, value: datetime.date, tags_mapping: _AnsibleTagsMapping) -> _AnsibleTaggedDate:
|
788
|
+
instance = cls(
|
789
|
+
year=value.year,
|
790
|
+
month=value.month,
|
791
|
+
day=value.day,
|
792
|
+
)
|
793
|
+
|
794
|
+
instance._ansible_tags_mapping = tags_mapping
|
795
|
+
|
796
|
+
return instance
|
797
|
+
|
798
|
+
def _native_copy(self) -> datetime.date:
|
799
|
+
return datetime.date(
|
800
|
+
year=self.year,
|
801
|
+
month=self.month,
|
802
|
+
day=self.day,
|
803
|
+
)
|
804
|
+
|
805
|
+
def __new__(cls, year, *args, **kwargs):
|
806
|
+
return super()._new(year, *args, **kwargs)
|
807
|
+
|
808
|
+
def __reduce__(self) -> tuple:
|
809
|
+
return super()._reduce(super().__reduce__())
|
810
|
+
|
811
|
+
def __repr__(self) -> str:
|
812
|
+
return self._native_copy().__repr__()
|
813
|
+
|
814
|
+
|
815
|
+
class _AnsibleTaggedTime(datetime.time, AnsibleTaggedObject):
|
816
|
+
__slots__ = _ANSIBLE_TAGGED_OBJECT_SLOTS
|
817
|
+
|
818
|
+
@classmethod
|
819
|
+
def _instance_factory(cls, value: datetime.time, tags_mapping: _AnsibleTagsMapping) -> _AnsibleTaggedTime:
|
820
|
+
instance = cls(
|
821
|
+
hour=value.hour,
|
822
|
+
minute=value.minute,
|
823
|
+
second=value.second,
|
824
|
+
microsecond=value.microsecond,
|
825
|
+
tzinfo=value.tzinfo,
|
826
|
+
fold=value.fold,
|
827
|
+
)
|
828
|
+
|
829
|
+
instance._ansible_tags_mapping = tags_mapping
|
830
|
+
|
831
|
+
return instance
|
832
|
+
|
833
|
+
def _native_copy(self) -> datetime.time:
|
834
|
+
return datetime.time(
|
835
|
+
hour=self.hour,
|
836
|
+
minute=self.minute,
|
837
|
+
second=self.second,
|
838
|
+
microsecond=self.microsecond,
|
839
|
+
tzinfo=self.tzinfo,
|
840
|
+
fold=self.fold,
|
841
|
+
)
|
842
|
+
|
843
|
+
def __new__(cls, hour, *args, **kwargs):
|
844
|
+
return super()._new(hour, *args, **kwargs)
|
845
|
+
|
846
|
+
def __reduce_ex__(self, protocol: t.SupportsIndex) -> tuple:
|
847
|
+
return super()._reduce(super().__reduce_ex__(protocol))
|
848
|
+
|
849
|
+
def __repr__(self) -> str:
|
850
|
+
return self._native_copy().__repr__()
|
851
|
+
|
852
|
+
|
853
|
+
class _AnsibleTaggedDict(dict, AnsibleTaggedObject):
|
854
|
+
__slots__ = _ANSIBLE_TAGGED_OBJECT_SLOTS
|
855
|
+
|
856
|
+
_item_source: t.ClassVar[t.Optional[t.Callable]] = dict.items
|
857
|
+
|
858
|
+
def __copy__(self):
|
859
|
+
return super()._copy_collection()
|
860
|
+
|
861
|
+
def copy(self) -> _AnsibleTaggedDict:
|
862
|
+
return copy.copy(self)
|
863
|
+
|
864
|
+
# NB: Tags are intentionally not preserved for operator methods that return a new instance. In-place operators ignore tags from the `other` instance.
|
865
|
+
# Propagation of tags in these cases is left to the caller, based on needs specific to their use case.
|
866
|
+
|
867
|
+
|
868
|
+
class _AnsibleTaggedList(list, AnsibleTaggedObject):
|
869
|
+
__slots__ = _ANSIBLE_TAGGED_OBJECT_SLOTS
|
870
|
+
|
871
|
+
def __copy__(self):
|
872
|
+
return super()._copy_collection()
|
873
|
+
|
874
|
+
def copy(self) -> _AnsibleTaggedList:
|
875
|
+
return copy.copy(self)
|
876
|
+
|
877
|
+
# NB: Tags are intentionally not preserved for operator methods that return a new instance. In-place operators ignore tags from the `other` instance.
|
878
|
+
# Propagation of tags in these cases is left to the caller, based on needs specific to their use case.
|
879
|
+
|
880
|
+
|
881
|
+
# DTFIX-RELEASE: do we want frozenset too?
|
882
|
+
class _AnsibleTaggedSet(set, AnsibleTaggedObject):
|
883
|
+
__slots__ = _ANSIBLE_TAGGED_OBJECT_SLOTS
|
884
|
+
|
885
|
+
def __copy__(self):
|
886
|
+
return super()._copy_collection()
|
887
|
+
|
888
|
+
def copy(self):
|
889
|
+
return copy.copy(self)
|
890
|
+
|
891
|
+
def __init__(self, value=None, *args, **kwargs):
|
892
|
+
if type(value) is _AnsibleTagsMapping: # pylint: disable=unidiomatic-typecheck
|
893
|
+
super().__init__(*args, **kwargs)
|
894
|
+
else:
|
895
|
+
super().__init__(value, *args, **kwargs)
|
896
|
+
|
897
|
+
def __new__(cls, value=None, *args, **kwargs):
|
898
|
+
return super()._new(value, *args, **kwargs)
|
899
|
+
|
900
|
+
def __reduce_ex__(self, protocol: t.SupportsIndex) -> tuple:
|
901
|
+
return super()._reduce(super().__reduce_ex__(protocol))
|
902
|
+
|
903
|
+
def __str__(self) -> str:
|
904
|
+
return self._native_copy().__str__()
|
905
|
+
|
906
|
+
def __repr__(self) -> str:
|
907
|
+
return self._native_copy().__repr__()
|
908
|
+
|
909
|
+
|
910
|
+
class _AnsibleTaggedTuple(tuple, AnsibleTaggedObject):
|
911
|
+
# nonempty __slots__ not supported for subtype of 'tuple'
|
912
|
+
|
913
|
+
def __copy__(self):
|
914
|
+
return super()._copy_collection()
|
915
|
+
|
916
|
+
|
917
|
+
# This set gets augmented with additional types when some controller-only types are imported.
|
918
|
+
# While we could proxy or subclass builtin singletons, they're idiomatically compared with "is" reference
|
919
|
+
# equality, which we can't customize.
|
920
|
+
_untaggable_types = {type(None), bool}
|
921
|
+
|
922
|
+
# noinspection PyProtectedMember
|
923
|
+
_ANSIBLE_ALLOWED_VAR_TYPES = frozenset({type(None), bool}) | set(AnsibleTaggedObject._tagged_type_map) | set(AnsibleTaggedObject._tagged_type_map.values())
|
924
|
+
"""These are the only types supported by Ansible's variable storage. Subclasses are not permitted."""
|
925
|
+
|
926
|
+
_ANSIBLE_ALLOWED_NON_SCALAR_COLLECTION_VAR_TYPES = frozenset(item for item in _ANSIBLE_ALLOWED_VAR_TYPES if is_non_scalar_collection_type(item))
|
927
|
+
_ANSIBLE_ALLOWED_MAPPING_VAR_TYPES = frozenset(item for item in _ANSIBLE_ALLOWED_VAR_TYPES if issubclass(item, c.Mapping))
|
928
|
+
_ANSIBLE_ALLOWED_SCALAR_VAR_TYPES = _ANSIBLE_ALLOWED_VAR_TYPES - _ANSIBLE_ALLOWED_NON_SCALAR_COLLECTION_VAR_TYPES
|