diff --git a/core/wren/src/wren/cli.py b/core/wren/src/wren/cli.py index d73ac5600..6ad130a3e 100644 --- a/core/wren/src/wren/cli.py +++ b/core/wren/src/wren/cli.py @@ -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) diff --git a/core/wren/src/wren/config.py b/core/wren/src/wren/config.py index ed0e942fe..6faee51c8 100644 --- a/core/wren/src/wren/config.py +++ b/core/wren/src/wren/config.py @@ -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, diff --git a/core/wren/src/wren/context.py b/core/wren/src/wren/context.py index ea7d5eb8a..8d7a4f69c 100644 --- a/core/wren/src/wren/context.py +++ b/core/wren/src/wren/context.py @@ -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 diff --git a/core/wren/src/wren/context_cli.py b/core/wren/src/wren/context_cli.py index 32a815be9..39428a626 100644 --- a/core/wren/src/wren/context_cli.py +++ b/core/wren/src/wren/context_cli.py @@ -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 diff --git a/core/wren/src/wren/cube_cli.py b/core/wren/src/wren/cube_cli.py index 79c8e9069..ee722ab06 100644 --- a/core/wren/src/wren/cube_cli.py +++ b/core/wren/src/wren/cube_cli.py @@ -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) diff --git a/core/wren/src/wren/dbt.py b/core/wren/src/wren/dbt.py index 4019a914d..49f086bdd 100644 --- a/core/wren/src/wren/dbt.py +++ b/core/wren/src/wren/dbt.py @@ -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 ) diff --git a/core/wren/src/wren/memory/cli.py b/core/wren/src/wren/memory/cli.py index 406d86450..9acb5d1c8 100644 --- a/core/wren/src/wren/memory/cli.py +++ b/core/wren/src/wren/memory/cli.py @@ -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) diff --git a/core/wren/src/wren/osi.py b/core/wren/src/wren/osi.py index 5f4e470b1..a4f84bac9 100644 --- a/core/wren/src/wren/osi.py +++ b/core/wren/src/wren/osi.py @@ -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: diff --git a/core/wren/src/wren/profile.py b/core/wren/src/wren/profile.py index f19260c54..41ed774cc 100644 --- a/core/wren/src/wren/profile.py +++ b/core/wren/src/wren/profile.py @@ -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 diff --git a/core/wren/src/wren/profile_cli.py b/core/wren/src/wren/profile_cli.py index e93f0610d..81c891060 100644 --- a/core/wren/src/wren/profile_cli.py +++ b/core/wren/src/wren/profile_cli.py @@ -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: diff --git a/core/wren/src/wren/skills_content/dlt-connector/scripts/introspect_dlt.py b/core/wren/src/wren/skills_content/dlt-connector/scripts/introspect_dlt.py index 08bf56d27..a1a35f36c 100644 --- a/core/wren/src/wren/skills_content/dlt-connector/scripts/introspect_dlt.py +++ b/core/wren/src/wren/skills_content/dlt-connector/scripts/introspect_dlt.py @@ -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//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" diff --git a/core/wren/src/wren/utils_cli.py b/core/wren/src/wren/utils_cli.py index f1edac19d..d2a4d2b83 100644 --- a/core/wren/src/wren/utils_cli.py +++ b/core/wren/src/wren/utils_cli.py @@ -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: