Fix YAML string loading using Python loader

This commit is contained in:
Matt Clay 2026-01-14 16:39:49 -08:00
parent 330f40b4f5
commit 52ed1759fb
4 changed files with 34 additions and 4 deletions

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.

View file

@ -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."""

View file

@ -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 "${@}"

View file

@ -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'}"