Skip to content

Commit 2defa5b

Browse files
authored
Use ruamel.yaml.clib as default (#4712)
It seems the only thing we need to do to enable this is to pull in this dependency and switch to using `safe` type by default. This would be a better apple-to-apple comparison with #4486 since that one uses a C library for handling json. Note that the clib does not respect `.indent` formatting hints > ### Indenting using typ="safe" > > The C based emitter doesn’t have the fine control, distinguishing between block mappings and sequences. Do only use the pure Python versions of the dumper if you want to have that sort of control. Reference: - Upstream documentation: https://yaml.dev/doc/ruamel.yaml/ - Note about `yaml.indent` https://yaml.dev/doc/ruamel.yaml/detail Related to #4406 --------- Signed-off-by: Cristian Le <git@lecris.dev>
1 parent 45e9150 commit 2defa5b

7 files changed

Lines changed: 35 additions & 23 deletions

File tree

pylock.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1924,6 +1924,7 @@ name = "pylero"
19241924
version = "0.1.1"
19251925
index = "https://pypi.org/simple"
19261926
sdist = { url = "https://files.pythonhosted.org/packages/88/38/0c0ccaafbb8594cf50af8d2376a5afee9e7279b7715a928558e7b52eb6f6/pylero-0.1.1.tar.gz", upload-time = 2025-05-30T13:38:52Z, size = 309121, hashes = { sha256 = "de0ccd37da69e50993fe403eca5d093d70c57319640d6af6403ab9a3496ae16c" } }
1927+
wheels = [{ url = "https://files.pythonhosted.org/packages/0b/79/4a7ff895325f7226f846cf4e274be9bbeedcd2d9804027c5025e72134ff4/pylero-0.1.1-py3-none-any.whl", upload-time = 2025-12-19T15:11:28Z, size = 101338, hashes = { sha256 = "ada04668e36adaaed950e213699f8442466994142c127a3f07b5e4d19fc9709f" } }]
19271928

19281929
[[packages]]
19291930
name = "python-bugzilla"
@@ -2402,7 +2403,6 @@ wheels = [{ name = "ruamel_yaml-0.18.16-py3-none-any.whl", url = "https://files.
24022403
[[packages]]
24032404
name = "ruamel-yaml-clib"
24042405
version = "0.2.15"
2405-
marker = "python_full_version < '3.14' and platform_python_implementation == 'CPython'"
24062406
index = "https://pypi.org/simple"
24072407
sdist = { url = "https://files.pythonhosted.org/packages/ea/97/60fda20e2fb54b83a61ae14648b0817c8f5d84a3821e40bfbdae1437026a/ruamel_yaml_clib-0.2.15.tar.gz", upload-time = 2025-11-16T16:12:59Z, size = 225794, hashes = { sha256 = "46e4cc8c43ef6a94885f72512094e482114a8a706d3c555a34ed4b0d20200600" } }
24082408
wheels = [

pyproject.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ dependencies = [
4848
"pygments>=2.7.4",
4949
"requests>=2.25.1",
5050
"ruamel.yaml>=0.16.6",
51+
"ruamel-yaml-clib",
5152
"urllib3>=1.26.5, <3.0",
5253
"typing-extensions>=4; python_version < '3.13'",
5354
]

tests/plan/export/test.sh

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -16,19 +16,19 @@ rlJournalStart
1616

1717
rlPhaseStartTest
1818
rlRun -s "tmt plan export ." 0 "Export plan"
19-
rlAssertGrep "- name: /plan/basic" $rlRun_LOG
20-
rlAssertGrep "- name: /plan/context" $rlRun_LOG
21-
rlAssertGrep "- name: /plan/environment" $rlRun_LOG
22-
rlAssertGrep "- name: /plan/gate" $rlRun_LOG
23-
rlAssertGrep "- name: /plan/extra-keys" $rlRun_LOG
19+
rlAssertGrep "name: /plan/basic" $rlRun_LOG
20+
rlAssertGrep "name: /plan/context" $rlRun_LOG
21+
rlAssertGrep "name: /plan/environment" $rlRun_LOG
22+
rlAssertGrep "name: /plan/gate" $rlRun_LOG
23+
rlAssertGrep "name: /plan/extra-keys" $rlRun_LOG
2424
rlAssertGrep "discover:" $rlRun_LOG
2525
rlAssertGrep "execute:" $rlRun_LOG
2626
assert_internal_fields "$rlRun_LOG"
2727
rlPhaseEnd
2828

2929
rlPhaseStartTest "tmt plan export /plan/basic"
3030
rlRun -s "tmt plan export /plan/basic" 0 "Export plan"
31-
rlAssertGrep "- name: /plan/basic" $rlRun_LOG
31+
rlAssertGrep "name: /plan/basic" $rlRun_LOG
3232
rlAssertGrep "summary: Just basic keys." $rlRun_LOG
3333
rlAssertGrep "discover:" $rlRun_LOG
3434
rlAssertGrep "execute:" $rlRun_LOG
@@ -37,7 +37,7 @@ rlJournalStart
3737

3838
rlPhaseStartTest "tmt plan export /plan/context"
3939
rlRun -s "tmt --context cmd-context=value plan export /plan/context" 0 "Export plan"
40-
rlAssertGrep "- name: /plan/context" $rlRun_LOG
40+
rlAssertGrep "name: /plan/context" $rlRun_LOG
4141
rlAssertGrep "summary: Define context" $rlRun_LOG
4242
rlAssertGrep "discover:" $rlRun_LOG
4343
rlAssertGrep "execute:" $rlRun_LOG
@@ -49,7 +49,7 @@ rlJournalStart
4949

5050
rlPhaseStartTest "tmt plan export /plan/environment"
5151
rlRun -s "tmt plan export /plan/environment" 0 "Export plan"
52-
rlAssertGrep "- name: /plan/environment" $rlRun_LOG
52+
rlAssertGrep "name: /plan/environment" $rlRun_LOG
5353
rlAssertGrep "summary: Define environment" $rlRun_LOG
5454
rlAssertGrep "discover:" $rlRun_LOG
5555
rlAssertGrep "execute:" $rlRun_LOG
@@ -60,7 +60,7 @@ rlJournalStart
6060

6161
rlPhaseStartTest "tmt plan export /plan/gate"
6262
rlRun -s "tmt plan export /plan/gate" 0 "Export plan"
63-
rlAssertGrep "- name: /plan/gate" $rlRun_LOG
63+
rlAssertGrep "name: /plan/gate" $rlRun_LOG
6464
rlAssertGrep "summary: Define gate" $rlRun_LOG
6565
rlAssertGrep "discover:" $rlRun_LOG
6666
rlAssertGrep "execute:" $rlRun_LOG
@@ -81,7 +81,7 @@ rlJournalStart
8181

8282
rlPhaseStartTest "tmt plan export /plan/extra-keys"
8383
rlRun -s "tmt plan export /plan/extra-keys" 0 "Export plan with extra- keys"
84-
rlAssertGrep "- name: /plan/extra-keys" $rlRun_LOG
84+
rlAssertGrep "name: /plan/extra-keys" $rlRun_LOG
8585
rlAssertGrep "summary: Plan with extra- keys" $rlRun_LOG
8686
rlAssertGrep "extra-reviewer: John Doe" $rlRun_LOG
8787
rlAssertGrep "extra-jira-id: TMT-123" $rlRun_LOG

tmt/base/core.py

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1712,12 +1712,13 @@ def lint_legacy_relevancy_rules(self) -> LinterReturn:
17121712
return
17131713

17141714
filename = self.fmf_sources[-1]
1715-
metadata: dict[str, Any] = tmt.utils.yaml_to_dict(self.read(filename))
1715+
# `rt` mode is needed here to preserve comments during the `fix`, see comment in `_yaml`
1716+
metadata: dict[str, Any] = tmt.utils.yaml_to_dict(self.read(filename), yaml_type="rt")
17161717

17171718
metadata['adjust'] = tmt.convert.relevancy_to_adjust(
17181719
metadata.pop('relevancy'), self._logger
17191720
)
1720-
self.write(filename, to_yaml(metadata))
1721+
self.write(filename, to_yaml(metadata, yaml_type="rt"))
17211722

17221723
yield LinterOutcome.FIXED, 'relevancy converted into adjust'
17231724

@@ -1812,7 +1813,8 @@ def lint_require_type_field(self) -> LinterReturn:
18121813
return
18131814

18141815
filename = self.fmf_sources[-1]
1815-
metadata: dict[str, Any] = tmt.utils.yaml_to_dict(self.read(filename))
1816+
# `rt` mode is needed here to preserve comments during the `fix`, see comment in `_yaml`
1817+
metadata: dict[str, Any] = tmt.utils.yaml_to_dict(self.read(filename), yaml_type="rt")
18161818

18171819
if not tmt.utils.is_key_origin(self.node, 'require') and all(
18181820
dependency in metadata.get('require', []) for dependency in missing_type
@@ -1832,7 +1834,7 @@ def lint_require_type_field(self) -> LinterReturn:
18321834

18331835
dependency['type'] = 'file' if dependency.get('pattern') else 'library'
18341836

1835-
self.write(filename, to_yaml(metadata))
1837+
self.write(filename, to_yaml(metadata, yaml_type="rt"))
18361838

18371839
yield LinterOutcome.FIXED, 'added type to requirements'
18381840

tmt/steps/report/reportportal.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@
4747
]
4848

4949

50-
def yaml_to_dict(data: str, yaml_type: Optional[tmt.utils.YamlTypType] = None) -> dict[str, Any]:
50+
def yaml_to_dict(data: str, yaml_type: tmt.utils.YamlTypType = "safe") -> dict[str, Any]:
5151
d: dict[str, Any] = _yaml_to_dict(data, yaml_type=yaml_type)
5252

5353
return d

tmt/utils/__init__.py

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,6 @@
6262
import urllib3.util.retry
6363
from click import echo, wrap_text
6464
from ruamel.yaml import YAML
65-
from ruamel.yaml.comments import CommentedMap
6665
from ruamel.yaml.parser import ParserError
6766
from ruamel.yaml.representer import Representer
6867
from urllib3.response import HTTPResponse
@@ -3504,7 +3503,7 @@ def _yaml_represent_path(representer: Representer, path: Path) -> Any:
35043503

35053504
def _yaml(
35063505
*,
3507-
yaml_type: Optional[YamlTypType] = None,
3506+
yaml_type: YamlTypType = "safe",
35083507
width: Optional[int] = None,
35093508
start: bool = False,
35103509
) -> YAML:
@@ -3522,6 +3521,9 @@ def _yaml(
35223521
:returns: a loader/dumper instance.
35233522
"""
35243523

3524+
# We use the safe mode as default in order to use CParser from ruamel.yaml.clib
3525+
# Note that we cannot mix yaml_type loader and dumper because roundtrip data is not
3526+
# serializable to safe.
35253527
yaml = YAML(typ=yaml_type)
35263528

35273529
# Setting `mapping` and `offset` may lead to a subpar-looking YAML
@@ -3592,7 +3594,9 @@ def _sanitize_yaml_tree(value: Any, sort_keys: bool) -> Any:
35923594
if isinstance(value, MutableMapping):
35933595
if sort_keys:
35943596
# Sort the data https://stackoverflow.com/a/40227545
3595-
sorted_value = CommentedMap()
3597+
# TODO: This needs to be CommentedMap if using roundtrip, but it is incompatible with
3598+
# safe yaml mode.
3599+
sorted_value = {}
35963600

35973601
for key in sorted(value):
35983602
sorted_value[key] = value[key]
@@ -3627,7 +3631,7 @@ def _sanitize_yaml_tree(value: Any, sort_keys: bool) -> Any:
36273631
def to_yaml(
36283632
data: Any,
36293633
*,
3630-
yaml_type: Optional[YamlTypType] = None,
3634+
yaml_type: YamlTypType = "safe",
36313635
width: Optional[int] = None,
36323636
sort: bool = False,
36333637
start: bool = False,
@@ -3657,7 +3661,7 @@ def to_yaml(
36573661
return output.getvalue()
36583662

36593663

3660-
def from_yaml(data: str, *, yaml_type: Optional[YamlTypType] = None) -> Any:
3664+
def from_yaml(data: str, *, yaml_type: YamlTypType = "safe") -> Any:
36613665
"""
36623666
Convert a YAML content into the corresponding Python data structures.
36633667
@@ -3674,7 +3678,7 @@ def from_yaml(data: str, *, yaml_type: Optional[YamlTypType] = None) -> Any:
36743678
raise GeneralError('Invalid YAML syntax.') from error
36753679

36763680

3677-
def yaml_to_dict(data: str, *, yaml_type: Optional[YamlTypType] = None) -> dict[Any, Any]:
3681+
def yaml_to_dict(data: str, *, yaml_type: YamlTypType = "safe") -> dict[Any, Any]:
36783682
"""
36793683
Convert a YAML content into a Python dictionary.
36803684
@@ -3700,7 +3704,7 @@ def yaml_to_dict(data: str, *, yaml_type: Optional[YamlTypType] = None) -> dict[
37003704
return loaded_data
37013705

37023706

3703-
def yaml_to_list(data: str, *, yaml_type: Optional[YamlTypType] = 'safe') -> list[Any]:
3707+
def yaml_to_list(data: str, *, yaml_type: YamlTypType = 'safe') -> list[Any]:
37043708
"""
37053709
Convert a YAML content into a Python list.
37063710

uv.lock

Lines changed: 5 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)