From 52ed1759fb5c68bc7b667f23f9a5aaa44ea64bee Mon Sep 17 00:00:00 2001 From: Matt Clay Date: Wed, 14 Jan 2026 16:39:49 -0800 Subject: [PATCH] Fix YAML string loading using Python loader --- changelogs/fragments/pyyaml-name.yml | 2 ++ lib/ansible/_internal/_yaml/_loader.py | 17 +++++++++++++---- test/integration/targets/pyyaml/runme.sh | 10 ++++++++++ test/integration/targets/pyyaml/runme.yml | 9 +++++++++ 4 files changed, 34 insertions(+), 4 deletions(-) create mode 100644 changelogs/fragments/pyyaml-name.yml create mode 100644 test/integration/targets/pyyaml/runme.yml diff --git a/changelogs/fragments/pyyaml-name.yml b/changelogs/fragments/pyyaml-name.yml new file mode 100644 index 00000000000..61dc2da0b28 --- /dev/null +++ b/changelogs/fragments/pyyaml-name.yml @@ -0,0 +1,2 @@ +bugfixes: + - yaml loading - Fix traceback when parsing YAML strings (not files) when using the pure Python implementation of PyYAML. diff --git a/lib/ansible/_internal/_yaml/_loader.py b/lib/ansible/_internal/_yaml/_loader.py index fa14006c0f8..226882d253d 100644 --- a/lib/ansible/_internal/_yaml/_loader.py +++ b/lib/ansible/_internal/_yaml/_loader.py @@ -13,22 +13,20 @@ from ._constructor import AnsibleConstructor, AnsibleInstrumentedConstructor if HAS_LIBYAML: from yaml.cyaml import CParser - class _YamlParser(CParser): + class _Parser(CParser): def __init__(self, stream: str | bytes | _io.IOBase) -> None: if isinstance(stream, (str, bytes)): stream = AnsibleTagHelper.untag(stream) # PyYAML + libyaml barfs on str/bytes subclasses CParser.__init__(self, stream) - self.name = getattr(stream, 'name', None) # provide feature parity with the Python implementation (yaml.reader.Reader provides name) - else: from yaml.composer import Composer from yaml.reader import Reader from yaml.scanner import Scanner from yaml.parser import Parser - class _YamlParser(Reader, Scanner, Parser, Composer): # type: ignore[no-redef] + class _Parser(Reader, Scanner, Parser, Composer): # type: ignore[no-redef] def __init__(self, stream: str | bytes | _io.IOBase) -> None: Reader.__init__(self, stream) Scanner.__init__(self) @@ -36,6 +34,17 @@ else: Composer.__init__(self) +class _YamlParser(_Parser): + def __init__(self, stream: str | bytes | _io.IOBase) -> None: + super().__init__(stream) + + # The Python implementation of PyYAML (yaml.reader.Reader) provides self.name. + # However, it will fall back to "<...>" in various cases. + # The C implementation of PyYAML does not provide self.name. + # To provide consistency, name retrieval is re-implemented here. + self.name = getattr(stream, 'name', None) + + class AnsibleInstrumentedLoader(_YamlParser, AnsibleInstrumentedConstructor, Resolver): """Ansible YAML loader which supports Ansible custom behavior such as `Origin` tagging, but no Ansible-specific YAML tags.""" diff --git a/test/integration/targets/pyyaml/runme.sh b/test/integration/targets/pyyaml/runme.sh index d08385d6092..a5c74da01a6 100755 --- a/test/integration/targets/pyyaml/runme.sh +++ b/test/integration/targets/pyyaml/runme.sh @@ -4,8 +4,18 @@ set -eu -o pipefail source virtualenv.sh set +x +# Verify libyaml is in use. +ansible --version | tee /dev/stderr | grep 'with libyaml' + +# Run tests with libyaml. +ansible-playbook runme.yml "${@}" + # deps are already installed, using --no-deps to avoid re-installing them # Install PyYAML without libyaml to validate ansible can run PYYAML_FORCE_LIBYAML=0 pip install --no-binary PyYAML --ignore-installed --no-cache-dir --no-deps PyYAML +# Verify libyaml is not in use. ansible --version | tee /dev/stderr | grep 'without libyaml' + +# Run tests without libyaml. +ansible-playbook runme.yml "${@}" diff --git a/test/integration/targets/pyyaml/runme.yml b/test/integration/targets/pyyaml/runme.yml new file mode 100644 index 00000000000..168d473dc56 --- /dev/null +++ b/test/integration/targets/pyyaml/runme.yml @@ -0,0 +1,9 @@ +- hosts: localhost + gather_facts: no + tasks: + - name: Read YAML from a string + assert: + that: + - "'1' | from_yaml == 1" + - "'[1]' | from_yaml == [1]" + - "'key: value' | from_yaml == {'key': 'value'}"