[stable-2.20] Fix YAML string loading using Python loader (#86421) (#86575)

(cherry picked from commit 7d281b2a7c)
This commit is contained in:
Matt Clay
2026-03-12 23:16:53 +00:00
committed by GitHub
parent 7d174104be
commit 35c453bc8e
4 changed files with 34 additions and 4 deletions
+2
View File
@@ -0,0 +1,2 @@
bugfixes:
- yaml loading - Fix traceback when parsing YAML strings (not files) when using the pure Python implementation of PyYAML.
+13 -4
View File
@@ -13,22 +13,20 @@ from ._constructor import AnsibleConstructor, AnsibleInstrumentedConstructor
if HAS_LIBYAML: if HAS_LIBYAML:
from yaml.cyaml import CParser from yaml.cyaml import CParser
class _YamlParser(CParser): class _Parser(CParser):
def __init__(self, stream: str | bytes | _io.IOBase) -> None: def __init__(self, stream: str | bytes | _io.IOBase) -> None:
if isinstance(stream, (str, bytes)): if isinstance(stream, (str, bytes)):
stream = AnsibleTagHelper.untag(stream) # PyYAML + libyaml barfs on str/bytes subclasses stream = AnsibleTagHelper.untag(stream) # PyYAML + libyaml barfs on str/bytes subclasses
CParser.__init__(self, stream) CParser.__init__(self, stream)
self.name = getattr(stream, 'name', None) # provide feature parity with the Python implementation (yaml.reader.Reader provides name)
else: else:
from yaml.composer import Composer from yaml.composer import Composer
from yaml.reader import Reader from yaml.reader import Reader
from yaml.scanner import Scanner from yaml.scanner import Scanner
from yaml.parser import Parser 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: def __init__(self, stream: str | bytes | _io.IOBase) -> None:
Reader.__init__(self, stream) Reader.__init__(self, stream)
Scanner.__init__(self) Scanner.__init__(self)
@@ -36,6 +34,17 @@ else:
Composer.__init__(self) 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): class AnsibleInstrumentedLoader(_YamlParser, AnsibleInstrumentedConstructor, Resolver):
"""Ansible YAML loader which supports Ansible custom behavior such as `Origin` tagging, but no Ansible-specific YAML tags.""" """Ansible YAML loader which supports Ansible custom behavior such as `Origin` tagging, but no Ansible-specific YAML tags."""
+10
View File
@@ -4,8 +4,18 @@ set -eu -o pipefail
source virtualenv.sh source virtualenv.sh
set +x 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 # deps are already installed, using --no-deps to avoid re-installing them
# Install PyYAML without libyaml to validate ansible can run # 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 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' ansible --version | tee /dev/stderr | grep 'without libyaml'
# Run tests without libyaml.
ansible-playbook 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'}"