Skip to content

Commit ac04bec

Browse files
author
jc
committed
Using ruamel for round trip yaml file parsing
1 parent c381561 commit ac04bec

3 files changed

Lines changed: 61 additions & 65 deletions

File tree

pyproject.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ dependencies = [
2828
"psycopg2-binary>=2.9.11",
2929
"temporalio>=1.18.1",
3030
"requests>=2.32.5",
31+
"ruamel.yaml>=0.18.6",
3132
]
3233

3334
[build-system]

src/cli/deployment/helm_deployer/config_sync.py

Lines changed: 18 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -3,30 +3,17 @@
33
This module handles copying configuration files to the Helm staging area
44
and synchronizing settings between the application config and Helm values.
55
6-
Note on YAML editing:
7-
This module uses regex-based line editing to preserve comments and structure
8-
in values.yaml. If more complex YAML manipulation is needed in the future,
9-
consider using `ruamel.yaml` instead of `pyyaml`. ruamel.yaml preserves
10-
comments, ordering, and formatting when round-tripping YAML files:
11-
12-
from ruamel.yaml import YAML
13-
yaml = YAML()
14-
yaml.preserve_quotes = True
15-
with open(path) as f:
16-
data = yaml.load(f)
17-
# modify data...
18-
with open(path, 'w') as f:
19-
yaml.dump(data, f)
6+
Uses ruamel.yaml for YAML editing to preserve comments, ordering, and formatting
7+
when round-tripping YAML files.
208
"""
219

2210
from __future__ import annotations
2311

24-
import re
2512
import shutil
2613
from pathlib import Path
2714
from typing import TYPE_CHECKING
2815

29-
import yaml # type: ignore[import-untyped]
16+
from ruamel.yaml import YAML
3017

3118
from src.app.runtime.config.config_loader import load_config
3219

@@ -100,7 +87,7 @@ def _compute_config_changes(
10087
) -> list[str]:
10188
"""Compare and update values.yaml with config.yaml settings.
10289
103-
Uses line-based editing to preserve comments and formatting in values.yaml.
90+
Uses ruamel.yaml to preserve comments and formatting in values.yaml.
10491
10592
Args:
10693
config_path: Path to config.yaml
@@ -112,78 +99,44 @@ def _compute_config_changes(
11299
config_raw = load_config(config_path, processed=False)
113100
config_data = config_raw if isinstance(config_raw, dict) else {}
114101

102+
# Set up ruamel.yaml for round-trip (comment-preserving) mode
103+
yaml = YAML()
104+
yaml.preserve_quotes = True
105+
115106
# Read values.yaml for comparison (to detect what needs changing)
116107
with open(values_path) as f:
117-
values_data = yaml.safe_load(f)
108+
values_data = yaml.load(f)
118109

119110
changes = []
120-
updates: dict[str, tuple[bool, bool]] = {} # key -> (old_val, new_val)
111+
modified = False
121112

122113
# Check redis.enabled
123114
if redis_config := config_data.get("config", {}).get("redis"):
124115
if "redis" in values_data:
125116
old_val = values_data["redis"].get("enabled", True)
126117
new_val = redis_config.get("enabled", True)
127118
if old_val != new_val:
128-
updates["redis"] = (old_val, new_val)
119+
values_data["redis"]["enabled"] = new_val
129120
changes.append(f"redis.enabled: {old_val}{new_val}")
121+
modified = True
130122

131123
# Check temporal.enabled
132124
if temporal_config := config_data.get("config", {}).get("temporal"):
133125
if "temporal" in values_data:
134126
old_val = values_data["temporal"].get("enabled", True)
135127
new_val = temporal_config.get("enabled", True)
136128
if old_val != new_val:
137-
updates["temporal"] = (old_val, new_val)
129+
values_data["temporal"]["enabled"] = new_val
138130
changes.append(f"temporal.enabled: {old_val}{new_val}")
131+
modified = True
139132

140-
if updates:
141-
self._update_values_preserving_format(values_path, updates)
133+
# Write back with preserved formatting if any changes were made
134+
if modified:
135+
with open(values_path, "w") as f:
136+
yaml.dump(values_data, f)
142137

143138
return changes
144139

145-
def _update_values_preserving_format(
146-
self,
147-
values_path: Path,
148-
updates: dict[str, tuple[bool, bool]],
149-
) -> None:
150-
"""Update values.yaml while preserving comments and formatting.
151-
152-
Uses line-based editing to find and update specific values without
153-
rewriting the entire file.
154-
155-
Args:
156-
values_path: Path to values.yaml
157-
updates: Dict of section_name -> (old_value, new_value) to update
158-
"""
159-
with open(values_path) as f:
160-
lines = f.readlines()
161-
162-
current_section: str | None = None
163-
modified_lines = []
164-
165-
for line in lines:
166-
# Detect top-level section (no leading whitespace, ends with :)
167-
stripped = line.rstrip()
168-
if stripped and not line[0].isspace() and stripped.endswith(":"):
169-
# Extract section name (e.g., "redis:" -> "redis")
170-
current_section = stripped[:-1].split("#")[0].strip()
171-
172-
# Check if this line is an 'enabled:' setting in a section we want to update
173-
if current_section in updates:
174-
# Match lines like " enabled: true" or " enabled: false"
175-
match = re.match(r"^(\s+enabled:\s*)(true|false)(.*)$", line)
176-
if match:
177-
indent, old_bool, rest = match.groups()
178-
_, new_val = updates[current_section]
179-
new_bool = "true" if new_val else "false"
180-
line = f"{indent}{new_bool}{rest}\n"
181-
182-
modified_lines.append(line)
183-
184-
with open(values_path, "w") as f:
185-
f.writelines(modified_lines)
186-
187140
def copy_config_files(self, progress_factory: type[Progress]) -> None:
188141
"""Copy configuration files to Helm staging area.
189142

uv.lock

Lines changed: 42 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)