"""
testing of anchors and the aliases referring to them
"""
import pytest
from textwrap import dedent
import platform
import srsly
from .roundtrip import (
round_trip,
dedent,
round_trip_load,
round_trip_dump,
YAML,
) # NOQA
def load(s):
return round_trip_load(dedent(s))
def compare(d, s):
assert round_trip_dump(d) == dedent(s)
class TestAnchorsAliases:
def test_anchor_id_renumber(self):
from srsly.ruamel_yaml.serializer import Serializer
assert Serializer.ANCHOR_TEMPLATE == "id%03d"
data = load(
"""
a: &id002
b: 1
c: 2
d: *id002
"""
)
compare(
data,
"""
a: &id001
b: 1
c: 2
d: *id001
""",
)
def test_template_matcher(self):
"""test if id matches the anchor template"""
from srsly.ruamel_yaml.serializer import templated_id
assert templated_id(u"id001")
assert templated_id(u"id999")
assert templated_id(u"id1000")
assert templated_id(u"id0001")
assert templated_id(u"id0000")
assert not templated_id(u"id02")
assert not templated_id(u"id000")
assert not templated_id(u"x000")
# def test_re_matcher(self):
# import re
# assert re.compile(u'id(?!000)\\d{3,}').match('id001')
# assert not re.compile(u'id(?!000\\d*)\\d{3,}').match('id000')
# assert re.compile(u'id(?!000$)\\d{3,}').match('id0001')
def test_anchor_assigned(self):
from srsly.ruamel_yaml.comments import CommentedMap
data = load(
"""
a: &id002
b: 1
c: 2
d: *id002
e: &etemplate
b: 1
c: 2
f: *etemplate
"""
)
d = data["d"]
assert isinstance(d, CommentedMap)
assert d.yaml_anchor() is None # got dropped as it matches pattern
e = data["e"]
assert isinstance(e, CommentedMap)
assert e.yaml_anchor().value == "etemplate"
assert e.yaml_anchor().always_dump is False
def test_anchor_id_retained(self):
data = load(
"""
a: &id002
b: 1
c: 2
d: *id002
e: &etemplate
b: 1
c: 2
f: *etemplate
"""
)
compare(
data,
"""
a: &id001
b: 1
c: 2
d: *id001
e: &etemplate
b: 1
c: 2
f: *etemplate
""",
)
@pytest.mark.skipif(
platform.python_implementation() == "Jython",
reason="Jython throws RepresenterError",
)
def test_alias_before_anchor(self):
from srsly.ruamel_yaml.composer import ComposerError
with pytest.raises(ComposerError):
data = load(
"""
d: *id002
a: &id002
b: 1
c: 2
"""
)
data = data
def test_anchor_on_sequence(self):
# as reported by Bjorn Stabell
# https://bitbucket.org/ruamel/yaml/issue/7/anchor-names-not-preserved
from srsly.ruamel_yaml.comments import CommentedSeq
data = load(
"""
nut1: &alice
- 1
- 2
nut2: &blake
- some data
- *alice
nut3:
- *blake
- *alice
"""
)
r = data["nut1"]
assert isinstance(r, CommentedSeq)
assert r.yaml_anchor() is not None
assert r.yaml_anchor().value == "alice"
merge_yaml = dedent(
"""
- &CENTER {x: 1, y: 2}
- &LEFT {x: 0, y: 2}
- &BIG {r: 10}
- &SMALL {r: 1}
# All the following maps are equal:
# Explicit keys
- x: 1
y: 2
r: 10
label: center/small
# Merge one map
- <<: *CENTER
r: 10
label: center/medium
# Merge multiple maps
- <<: [*CENTER, *BIG]
label: center/big
# Override
- <<: [*BIG, *LEFT, *SMALL]
x: 1
label: center/huge
"""
)
def test_merge_00(self):
data = load(self.merge_yaml)
d = data[4]
ok = True
for k in d:
for o in [5, 6, 7]:
x = d.get(k)
y = data[o].get(k)
if not isinstance(x, int):
x = x.split("/")[0]
y = y.split("/")[0]
if x != y:
ok = False
print("key", k, d.get(k), data[o].get(k))
assert ok
def test_merge_accessible(self):
from srsly.ruamel_yaml.comments import CommentedMap, merge_attrib
data = load(
"""
k: &level_2 { a: 1, b2 }
l: &level_1 { a: 10, c: 3 }
m:
<<: *level_1
c: 30
d: 40
"""
)
d = data["m"]
assert isinstance(d, CommentedMap)
assert hasattr(d, merge_attrib)
def test_merge_01(self):
data = load(self.merge_yaml)
compare(data, self.merge_yaml)
def test_merge_nested(self):
yaml = """
a:
<<: &content
1: plugh
2: plover
0: xyzzy
b:
<<: *content
"""
data = round_trip(yaml) # NOQA
def test_merge_nested_with_sequence(self):
yaml = """
a:
<<: &content
<<: &y2
1: plugh
2: plover
0: xyzzy
b:
<<: [*content, *y2]
"""
data = round_trip(yaml) # NOQA
def test_add_anchor(self):
from srsly.ruamel_yaml.comments import CommentedMap
data = CommentedMap()
data_a = CommentedMap()
data["a"] = data_a
data_a["c"] = 3
data["b"] = 2
data.yaml_set_anchor("klm", always_dump=True)
data["a"].yaml_set_anchor("xyz", always_dump=True)
compare(
data,
"""
&klm
a: &xyz
c: 3
b: 2
""",
)
# this is an error in PyYAML
def test_reused_anchor(self):
from srsly.ruamel_yaml.error import ReusedAnchorWarning
yaml = """
- &a
x: 1
- <<: *a
- &a
x: 2
- <<: *a
"""
with pytest.warns(ReusedAnchorWarning):
data = round_trip(yaml) # NOQA
def test_issue_130(self):
# issue 130 reported by Devid Fee
ys = dedent(
"""\
components:
server: &server_component
type: spark.server:ServerComponent
host: 0.0.0.0
port: 8000
shell: &shell_component
type: spark.shell:ShellComponent
services:
server: &server_service
<<: *server_component
shell: &shell_service
<<: *shell_component
components:
server: {<<: *server_service}
"""
)
data = srsly.ruamel_yaml.safe_load(ys)
assert data["services"]["shell"]["components"]["server"]["port"] == 8000
def test_issue_130a(self):
# issue 130 reported by Devid Fee
ys = dedent(
"""\
components:
server: &server_component
type: spark.server:ServerComponent
host: 0.0.0.0
port: 8000
shell: &shell_component
type: spark.shell:ShellComponent
services:
server: &server_service
<<: *server_component
port: 4000
shell: &shell_service
<<: *shell_component
components:
server: {<<: *server_service}
"""
)
data = srsly.ruamel_yaml.safe_load(ys)
assert data["services"]["shell"]["components"]["server"]["port"] == 4000
class TestMergeKeysValues:
yaml_str = dedent(
"""\
- &mx
a: x1
b: x2
c: x3
- &my
a: y1
b: y2 # masked by the one in &mx
d: y4
-
a: 1
<<: [*mx, *my]
m: 6
"""
)
# in the following d always has "expanded" the merges
def test_merge_for(self):
from srsly.ruamel_yaml import safe_load
d = safe_load(self.yaml_str)
data = round_trip_load(self.yaml_str)
count = 0
for x in data[2]:
count += 1
print(count, x)
assert count == len(d[2])
def test_merge_keys(self):
from srsly.ruamel_yaml import safe_load
d = safe_load(self.yaml_str)
data = round_trip_load(self.yaml_str)
count = 0
for x in data[2].keys():
count += 1
print(count, x)
assert count == len(d[2])
def test_merge_values(self):
from srsly.ruamel_yaml import safe_load
d = safe_load(self.yaml_str)
data = round_trip_load(self.yaml_str)
count = 0
for x in data[2].values():
count += 1
print(count, x)
assert count == len(d[2])
def test_merge_items(self):
from srsly.ruamel_yaml import safe_load
d = safe_load(self.yaml_str)
data = round_trip_load(self.yaml_str)
count = 0
for x in data[2].items():
count += 1
print(count, x)
assert count == len(d[2])
def test_len_items_delete(self):
from srsly.ruamel_yaml import safe_load
from srsly.ruamel_yaml.compat import PY3
d = safe_load(self.yaml_str)
data = round_trip_load(self.yaml_str)
x = data[2].items()
print("d2 items", d[2].items(), len(d[2].items()), x, len(x))
ref = len(d[2].items())
print("ref", ref)
assert len(x) == ref
del data[2]["m"]
if PY3:
ref -= 1
assert len(x) == ref
del data[2]["d"]
if PY3:
ref -= 1
assert len(x) == ref
del data[2]["a"]
if PY3:
ref -= 1
assert len(x) == ref
def test_issue_196_cast_of_dict(self, capsys):
from srsly.ruamel_yaml import YAML
yaml = YAML()
mapping = yaml.load(
"""\
anchored: &anchor
a : 1
mapping:
<<: *anchor
b: 2
"""
)["mapping"]
for k in mapping:
print("k", k)
for k in mapping.copy():
print("kc", k)
print("v", list(mapping.keys()))
print("v", list(mapping.values()))
print("v", list(mapping.items()))
print(len(mapping))
print("-----")
# print({**mapping})
# print(type({**mapping}))
# assert 'a' in {**mapping}
assert "a" in mapping
x = {}
for k in mapping:
x[k] = mapping[k]
assert "a" in x
assert "a" in mapping.keys()
assert mapping["a"] == 1
assert mapping.__getitem__("a") == 1
assert "a" in dict(mapping)
assert "a" in dict(mapping.items())
def test_values_of_merged(self):
from srsly.ruamel_yaml import YAML
yaml = YAML()
data = yaml.load(dedent(self.yaml_str))
assert list(data[2].values()) == [1, 6, "x2", "x3", "y4"]
def test_issue_213_copy_of_merge(self):
from srsly.ruamel_yaml import YAML
yaml = YAML()
d = yaml.load(
"""\
foo: &foo
a: a
foo2:
<<: *foo
b: b
"""
)["foo2"]
assert d["a"] == "a"
d2 = d.copy()
assert d2["a"] == "a"
print("d", d)
del d["a"]
assert "a" not in d
assert "a" in d2
class TestDuplicateKeyThroughAnchor:
def test_duplicate_key_00(self):
from srsly.ruamel_yaml import version_info
from srsly.ruamel_yaml import safe_load, round_trip_load
from srsly.ruamel_yaml.constructor import (
DuplicateKeyFutureWarning,
DuplicateKeyError,
)
s = dedent(
"""\
&anchor foo:
foo: bar
*anchor : duplicate key
baz: bat
*anchor : duplicate key
"""
)
if version_info < (0, 15, 1):
pass
elif version_info < (0, 16, 0):
with pytest.warns(DuplicateKeyFutureWarning):
safe_load(s)
with pytest.warns(DuplicateKeyFutureWarning):
round_trip_load(s)
else:
with pytest.raises(DuplicateKeyError):
safe_load(s)
with pytest.raises(DuplicateKeyError):
round_trip_load(s)
def test_duplicate_key_01(self):
# so issue https://stackoverflow.com/a/52852106/1307905
from srsly.ruamel_yaml import version_info
from srsly.ruamel_yaml.constructor import DuplicateKeyError
s = dedent(
"""\
- &name-name
a: 1
- &help-name
b: 2
- <<: *name-name
<<: *help-name
"""
)
if version_info < (0, 15, 1):
pass
else:
with pytest.raises(DuplicateKeyError):
yaml = YAML(typ="safe")
yaml.load(s)
with pytest.raises(DuplicateKeyError):
yaml = YAML()
yaml.load(s)
class TestFullCharSetAnchors:
def test_master_of_orion(self):
# https://bitbucket.org/ruamel/yaml/issues/72/not-allowed-in-anchor-names
# submitted by Shalon Wood
yaml_str = """
- collection: &Backend.Civilizations.RacialPerk
items:
- key: perk_population_growth_modifier
- *Backend.Civilizations.RacialPerk
"""
data = load(yaml_str) # NOQA
def test_roundtrip_00(self):
yaml_str = """
- &dotted.words.here
a: 1
b: 2
- *dotted.words.here
"""
data = round_trip(yaml_str) # NOQA
def test_roundtrip_01(self):
yaml_str = """
- &dotted.words.here[a, b]
- *dotted.words.here
"""
data = load(yaml_str) # NOQA
compare(data, yaml_str.replace("[", " [")) # an extra space is inserted