fix: ensure UTF-8 encoding for YAML file operations on Windows (#2357)

Co-authored-by: LeafJJ <leafjj@users.noreply.github.com>
This commit is contained in:
leafObject
2026-06-11 09:47:38 +08:00
committed by GitHub
parent d33917f046
commit 322bd798da
12 changed files with 74 additions and 36 deletions
+1 -1
View File
@@ -109,7 +109,7 @@ def _load_conn(
typer.echo(f"Error: connection file not found: {path_str}", err=True)
raise typer.Exit(1)
try:
conn = json.loads(path.read_text())
conn = json.loads(path.read_text(encoding="utf-8"))
except json.JSONDecodeError as e:
typer.echo(f"Error: invalid JSON in {path_str}: {e}", err=True)
raise typer.Exit(1)
+1 -1
View File
@@ -38,7 +38,7 @@ def load_config(wren_home: Path) -> WrenConfig:
return WrenConfig()
try:
raw = json.loads(config_path.read_text())
raw = json.loads(config_path.read_text(encoding="utf-8"))
except (UnicodeDecodeError, json.JSONDecodeError, OSError) as e:
raise WrenError(
ErrorCode.GENERIC_USER_ERROR,
+38 -18
View File
@@ -171,7 +171,10 @@ def convert_mdl_to_project(mdl_json: dict) -> list[ProjectFile]:
ProjectFile(
relative_path="wren_project.yml",
content=yaml.dump(
project_config, default_flow_style=False, sort_keys=False
project_config,
default_flow_style=False,
sort_keys=False,
allow_unicode=True,
),
)
)
@@ -197,7 +200,10 @@ def convert_mdl_to_project(mdl_json: dict) -> list[ProjectFile]:
ProjectFile(
relative_path=f"{dir_path}/metadata.yml",
content=yaml.dump(
model_snake, default_flow_style=False, sort_keys=False
model_snake,
default_flow_style=False,
sort_keys=False,
allow_unicode=True,
),
)
)
@@ -219,6 +225,7 @@ def convert_mdl_to_project(mdl_json: dict) -> list[ProjectFile]:
{"statement": statement},
default_flow_style=False,
sort_keys=False,
allow_unicode=True,
),
)
)
@@ -229,7 +236,10 @@ def convert_mdl_to_project(mdl_json: dict) -> list[ProjectFile]:
ProjectFile(
relative_path=f"{dir_path}/metadata.yml",
content=yaml.dump(
view_snake, default_flow_style=False, sort_keys=False
view_snake,
default_flow_style=False,
sort_keys=False,
allow_unicode=True,
),
)
)
@@ -245,6 +255,7 @@ def convert_mdl_to_project(mdl_json: dict) -> list[ProjectFile]:
{"relationships": rels_snake},
default_flow_style=False,
sort_keys=False,
allow_unicode=True,
),
)
)
@@ -335,7 +346,7 @@ def load_global_config() -> dict:
config_file = _WREN_HOME / "config.yml"
if not config_file.exists():
return {}
return yaml.safe_load(config_file.read_text()) or {}
return yaml.safe_load(config_file.read_text(encoding="utf-8")) or {}
def discover_project_path(explicit: str | None = None) -> Path:
@@ -382,7 +393,7 @@ def load_project_config(project_path: Path) -> dict:
config_file = project_path / PROJECT_FILE
if not config_file.exists():
return {}
return yaml.safe_load(config_file.read_text()) or {}
return yaml.safe_load(config_file.read_text(encoding="utf-8")) or {}
# Field order preferred when writing wren_project.yml back from a dict —
@@ -415,7 +426,9 @@ def save_project_config(project_path: Path, config: dict) -> None:
ordered[key] = value
(project_path / PROJECT_FILE).write_text(
yaml.safe_dump(ordered, default_flow_style=False, sort_keys=False)
yaml.safe_dump(
ordered, default_flow_style=False, sort_keys=False, allow_unicode=True
)
)
@@ -494,7 +507,7 @@ def _load_models_v1(project_path: Path) -> list[dict]:
return []
models = []
for f in sorted(models_dir.glob("*.yml")):
data = yaml.safe_load(f.read_text())
data = yaml.safe_load(f.read_text(encoding="utf-8"))
if isinstance(data, dict):
data["_source_dir"] = f.stem
models.append(data)
@@ -518,7 +531,7 @@ def _load_models_v2(project_path: Path) -> list[dict]:
meta_file = d / "metadata.yml"
if not meta_file.exists():
continue
model = yaml.safe_load(meta_file.read_text()) or {}
model = yaml.safe_load(meta_file.read_text(encoding="utf-8")) or {}
if not isinstance(model, dict):
continue
model["_source_dir"] = d.name
@@ -526,7 +539,7 @@ def _load_models_v2(project_path: Path) -> list[dict]:
# Merge ref_sql.sql if present (takes precedence)
ref_sql_file = d / "ref_sql.sql"
if ref_sql_file.exists():
sql_content = ref_sql_file.read_text().strip()
sql_content = ref_sql_file.read_text(encoding="utf-8").strip()
if sql_content:
model["ref_sql"] = sql_content
@@ -551,7 +564,7 @@ def _load_views_v1(project_path: Path) -> list[dict]:
views_file = project_path / "views.yml"
if not views_file.exists():
return []
data = yaml.safe_load(views_file.read_text()) or {}
data = yaml.safe_load(views_file.read_text(encoding="utf-8")) or {}
return data.get("views", []) if isinstance(data, dict) else []
@@ -572,7 +585,7 @@ def _load_views_v2(project_path: Path) -> list[dict]:
meta_file = d / "metadata.yml"
if not meta_file.exists():
continue
view = yaml.safe_load(meta_file.read_text()) or {}
view = yaml.safe_load(meta_file.read_text(encoding="utf-8")) or {}
if not isinstance(view, dict):
continue
view["_source_dir"] = d.name
@@ -580,7 +593,7 @@ def _load_views_v2(project_path: Path) -> list[dict]:
# Merge sql.yml if present (takes precedence)
sql_file = d / "sql.yml"
if sql_file.exists():
sql_data = yaml.safe_load(sql_file.read_text()) or {}
sql_data = yaml.safe_load(sql_file.read_text(encoding="utf-8")) or {}
if isinstance(sql_data, dict) and sql_data.get("statement"):
view["statement"] = sql_data["statement"]
@@ -607,7 +620,7 @@ def _load_cubes_v1(project_path: Path) -> list[dict]:
return []
cubes = []
for f in sorted(cubes_dir.glob("*.yml")):
data = yaml.safe_load(f.read_text())
data = yaml.safe_load(f.read_text(encoding="utf-8"))
if isinstance(data, dict):
data["_source_file"] = f.name
cubes.append(data)
@@ -647,7 +660,7 @@ def load_relationships(project_path: Path) -> list[dict]:
rel_file = project_path / "relationships.yml"
if not rel_file.exists():
return []
data = yaml.safe_load(rel_file.read_text()) or {}
data = yaml.safe_load(rel_file.read_text(encoding="utf-8")) or {}
return data.get("relationships", []) if isinstance(data, dict) else []
@@ -656,7 +669,7 @@ def load_instructions(project_path: Path) -> str | None:
inst_file = project_path / "instructions.md"
if not inst_file.exists():
return None
return inst_file.read_text().strip() or None
return inst_file.read_text(encoding="utf-8").strip() or None
# ── Build ─────────────────────────────────────────────────────────────────
@@ -1264,7 +1277,9 @@ def apply_upgrade(project_path: Path, result: UpgradeResult) -> None:
config = load_project_config(project_path)
config["schema_version"] = result.to_version
config_file = project_path / PROJECT_FILE
config_file.write_text(yaml.dump(config, default_flow_style=False, sort_keys=False))
config_file.write_text(
yaml.dump(config, default_flow_style=False, sort_keys=False, allow_unicode=True)
)
def _apply_v1_to_v2(project_path: Path) -> None:
@@ -1282,7 +1297,9 @@ def _apply_v1_to_v2(project_path: Path) -> None:
(model_dir / "ref_sql.sql").write_text(ref_sql.strip() + "\n")
(model_dir / "metadata.yml").write_text(
yaml.dump(model, default_flow_style=False, sort_keys=False)
yaml.dump(
model, default_flow_style=False, sort_keys=False, allow_unicode=True
)
)
# Delete old flat file
@@ -1307,13 +1324,16 @@ def _apply_v1_to_v2(project_path: Path) -> None:
{"statement": statement},
default_flow_style=False,
sort_keys=False,
allow_unicode=True,
)
)
elif statement:
view["statement"] = statement
(view_dir / "metadata.yml").write_text(
yaml.dump(view, default_flow_style=False, sort_keys=False)
yaml.dump(
view, default_flow_style=False, sort_keys=False, allow_unicode=True
)
)
# Delete old views.yml
+11 -3
View File
@@ -229,7 +229,7 @@ def init(
typer.echo(f"Error: {mdl_path} not found.", err=True)
raise typer.Exit(1)
mdl_json = json.loads(mdl_path.read_text())
mdl_json = json.loads(mdl_path.read_text(encoding="utf-8"))
files = convert_mdl_to_project(mdl_json)
try:
write_project_files(files, project_path, force=force)
@@ -710,7 +710,11 @@ def show(
elif output == "yaml":
# YAML output uses snake_case (native)
manifest = build_manifest(project_path)
typer.echo(_yaml.dump(manifest, default_flow_style=False, sort_keys=False))
typer.echo(
_yaml.dump(
manifest, default_flow_style=False, sort_keys=False, allow_unicode=True
)
)
else:
# Summary view
config = load_project_config(project_path)
@@ -1158,7 +1162,11 @@ def _show_from_osi(
typer.echo(json.dumps(manifest_json, indent=2, ensure_ascii=False))
return
if output == "yaml":
typer.echo(_yaml.dump(manifest, default_flow_style=False, sort_keys=False))
typer.echo(
_yaml.dump(
manifest, default_flow_style=False, sort_keys=False, allow_unicode=True
)
)
return
# Summary
+2 -2
View File
@@ -32,7 +32,7 @@ def _load_mdl_json(mdl: str | None) -> str:
path_str = _require_mdl(mdl)
path = Path(path_str).expanduser()
if path.exists():
return path.read_text()
return path.read_text(encoding="utf-8")
typer.echo(f"Error: MDL file not found: {path}", err=True)
raise typer.Exit(1)
@@ -126,7 +126,7 @@ def _load_cube_query_from(source: str) -> dict:
if not p.exists():
typer.echo(f"Error: CubeQuery file not found: {p}", err=True)
raise typer.Exit(1)
raw = p.read_text()
raw = p.read_text(encoding="utf-8")
label = str(p)
try:
data = json.loads(raw)
+9 -2
View File
@@ -375,7 +375,10 @@ def convert_dbt_project_to_wren_project(
ProjectFile(
relative_path="wren_project.yml",
content=yaml.dump(
project_config, default_flow_style=False, sort_keys=False
project_config,
default_flow_style=False,
sort_keys=False,
allow_unicode=True,
),
),
ProjectFile(
@@ -384,6 +387,7 @@ def convert_dbt_project_to_wren_project(
{"relationships": relationships},
default_flow_style=False,
sort_keys=False,
allow_unicode=True,
),
),
ProjectFile(
@@ -404,6 +408,7 @@ def convert_dbt_project_to_wren_project(
{"version": 1, "pairs": query_pairs},
default_flow_style=False,
sort_keys=False,
allow_unicode=True,
),
),
]
@@ -411,7 +416,9 @@ def convert_dbt_project_to_wren_project(
files.extend(
ProjectFile(
relative_path=f"models/{model['name']}/metadata.yml",
content=yaml.dump(model, default_flow_style=False, sort_keys=False),
content=yaml.dump(
model, default_flow_style=False, sort_keys=False, allow_unicode=True
),
)
for model in imported_models
)
+1 -1
View File
@@ -78,7 +78,7 @@ def _load_manifest(mdl: str | None) -> dict:
)
raise typer.Exit(1)
try:
return json.loads(mdl_path.read_text())
return json.loads(mdl_path.read_text(encoding="utf-8"))
except json.JSONDecodeError as e:
typer.echo(f"Error: invalid JSON in {mdl_path}: {e}", err=True)
raise typer.Exit(1)
+1 -1
View File
@@ -86,7 +86,7 @@ def parse_osi(text: str, *, suffix: str = ".yaml") -> dict:
def load_osi_file(path: Path) -> dict:
return parse_osi(path.read_text(), suffix=path.suffix)
return parse_osi(path.read_text(encoding="utf-8"), suffix=path.suffix)
def _extract_wren_block(custom_extensions: Any) -> dict:
+5 -3
View File
@@ -149,7 +149,7 @@ def _load_raw() -> dict:
if not _PROFILES_FILE.exists():
return {"active": None, "profiles": {}}
try:
data = yaml.safe_load(_PROFILES_FILE.read_text())
data = yaml.safe_load(_PROFILES_FILE.read_text(encoding="utf-8"))
except yaml.YAMLError as exc:
raise ValueError(
f"profiles.yml is not valid YAML: {exc}\n"
@@ -180,7 +180,9 @@ def _load_raw() -> dict:
def _save_raw(data: dict) -> None:
"""Write profiles.yml atomically with 0600 permissions."""
_WREN_HOME.mkdir(parents=True, exist_ok=True)
payload = yaml.dump(data, default_flow_style=False, sort_keys=False)
payload = yaml.dump(
data, default_flow_style=False, sort_keys=False, allow_unicode=True
)
# Write to a temp file in the same directory then atomically replace
fd, tmp_path = tempfile.mkstemp(dir=_WREN_HOME, suffix=".yml.tmp")
try:
@@ -232,7 +234,7 @@ def resolve_profile_for_project(project_path: Path) -> tuple[str | None, dict]:
pinned_name: str | None = None
if project_yml.exists():
try:
config = yaml.safe_load(project_yml.read_text()) or {}
config = yaml.safe_load(project_yml.read_text(encoding="utf-8")) or {}
except yaml.YAMLError as exc:
# Fail loudly: a malformed project file shouldn't silently fall
# back to the global active profile — that risks running against
+1 -1
View File
@@ -133,7 +133,7 @@ def add(
typer.echo(f"Error: file not found: {from_file}", err=True)
raise typer.Exit(1)
try:
text = path.read_text()
text = path.read_text(encoding="utf-8")
if path.suffix in (".yml", ".yaml"):
raw = yaml.safe_load(text)
else:
@@ -283,7 +283,7 @@ def generate_project_files(
"data_source": "duckdb",
}
files["wren_project.yml"] = yaml.dump(
project_config, default_flow_style=False, sort_keys=False
project_config, default_flow_style=False, sort_keys=False, allow_unicode=True
)
# -- models/<table_name>/metadata.yml --
@@ -316,7 +316,7 @@ def generate_project_files(
dir_name = _safe_path_segment(model_name)
files[f"models/{dir_name}/metadata.yml"] = yaml.dump(
model, default_flow_style=False, sort_keys=False
model, default_flow_style=False, sort_keys=False, allow_unicode=True
)
# -- relationships.yml --
@@ -337,6 +337,7 @@ def generate_project_files(
{"relationships": rels_yaml},
default_flow_style=False,
sort_keys=False,
allow_unicode=True,
)
else:
files["relationships.yml"] = "relationships: []\n"
+1 -1
View File
@@ -51,7 +51,7 @@ def parse_types_cmd(
if not path.exists():
typer.echo(f"Error: file not found: {input_file}", err=True)
raise typer.Exit(1)
data = json.loads(path.read_text())
data = json.loads(path.read_text(encoding="utf-8"))
else:
data = json.load(sys.stdin)
except json.JSONDecodeError as e: