mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-07-25 17:18:11 +00:00
fix(windows): sweep remaining bare read_text/write_text sites + linter rule
AST-driven pass over every Path.read_text()/write_text() without an explicit encoding= across non-test code: 71 sites in 34 files (skills_hub, hermes_cli/main+profiles+service_manager+container_boot, mem0/hindsight/honcho plugins, achievements dashboard, release/CI scripts, productivity+comfyui skill helpers, agent/*). Verified zero positional-encoding collisions before insertion; per-file compile() check after. Adds a check-windows-footguns rule flagging bare single-line read_text/write_text (multi-line forms stay covered by the AST guard test from #38985). Together with the salvaged contributor commits this retires the ~169-site bare file-I/O class (#37423's long tail).
This commit is contained in:
parent
adecb0d1a9
commit
75e0d52034
36 changed files with 103 additions and 75 deletions
|
|
@ -164,7 +164,7 @@ def _remove_env_source(provider: str, removed) -> RemovalResult:
|
|||
if env_path.exists():
|
||||
env_in_dotenv = any(
|
||||
line.strip().startswith(f"{env_var}=")
|
||||
for line in env_path.read_text(errors="replace").splitlines()
|
||||
for line in env_path.read_text(errors="replace", encoding="utf-8").splitlines()
|
||||
)
|
||||
except OSError:
|
||||
pass
|
||||
|
|
|
|||
|
|
@ -3066,7 +3066,7 @@ def refresh_systemd_unit_if_needed(system: bool = False) -> bool:
|
|||
if _refuse_temp_home_service_write(new_unit, "systemd unit"):
|
||||
return False
|
||||
|
||||
unit_path.write_text(new_unit, encoding="utf-8", encoding="utf-8")
|
||||
unit_path.write_text(new_unit, encoding="utf-8")
|
||||
_run_systemctl(["daemon-reload"], system=system, check=True, timeout=30)
|
||||
print(
|
||||
f"↻ Updated gateway {_service_scope_label(system)} service definition to match the current Hermes install"
|
||||
|
|
@ -3248,7 +3248,7 @@ def systemd_install(
|
|||
if _refuse_temp_home_service_write(new_unit, "systemd unit"):
|
||||
return
|
||||
print(f"Installing {_service_scope_label(system)} systemd service to: {unit_path}")
|
||||
unit_path.write_text(new_unit, encoding="utf-8", encoding="utf-8")
|
||||
unit_path.write_text(new_unit, encoding="utf-8")
|
||||
|
||||
_run_systemctl(["daemon-reload"], system=system, check=True, timeout=30)
|
||||
if enable_on_startup:
|
||||
|
|
@ -4082,7 +4082,7 @@ def refresh_launchd_plist_if_needed() -> bool:
|
|||
if _refuse_temp_home_service_write(new_plist, "launchd plist"):
|
||||
return False
|
||||
|
||||
plist_path.write_text(new_plist, encoding="utf-8", encoding="utf-8")
|
||||
plist_path.write_text(new_plist, encoding="utf-8")
|
||||
label = get_launchd_label()
|
||||
domain = _launchd_domain()
|
||||
target = f"{domain}/{label}"
|
||||
|
|
@ -4301,7 +4301,7 @@ def launchd_start():
|
|||
sys.exit(1)
|
||||
print("↻ launchd plist missing; regenerating service definition")
|
||||
plist_path.parent.mkdir(parents=True, exist_ok=True)
|
||||
plist_path.write_text(new_plist, encoding="utf-8", encoding="utf-8")
|
||||
plist_path.write_text(new_plist, encoding="utf-8")
|
||||
try:
|
||||
_launchctl_bootstrap(_launchd_domain(), plist_path, label, timeout=30)
|
||||
subprocess.run(
|
||||
|
|
|
|||
|
|
@ -9959,7 +9959,7 @@ def _ensure_fhs_path_guard() -> None:
|
|||
if not cfg.is_file():
|
||||
continue
|
||||
try:
|
||||
existing = cfg.read_text(errors="replace")
|
||||
existing = cfg.read_text(errors="replace", encoding="utf-8")
|
||||
except OSError:
|
||||
continue
|
||||
# Idempotency: skip if any uncommented PATH= line already references
|
||||
|
|
@ -12535,7 +12535,7 @@ def _cmd_update_impl(args, gateway_mode: bool):
|
|||
if gateway_mode:
|
||||
_exit_code_path = get_hermes_home() / ".update_exit_code"
|
||||
try:
|
||||
_exit_code_path.write_text("1")
|
||||
_exit_code_path.write_text("1", encoding="utf-8")
|
||||
except OSError:
|
||||
pass
|
||||
_warn_incomplete_gateway_fleet_restart(failed_or_stale_units)
|
||||
|
|
|
|||
|
|
@ -1601,7 +1601,7 @@ class PluginManager:
|
|||
init_file = plugin_dir / "__init__.py"
|
||||
if init_file.exists():
|
||||
try:
|
||||
source_text = init_file.read_text(errors="replace")[:8192]
|
||||
source_text = init_file.read_text(errors="replace", encoding="utf-8")[:8192]
|
||||
if (
|
||||
"register_memory_provider" in source_text
|
||||
or "MemoryProvider" in source_text
|
||||
|
|
|
|||
|
|
@ -78,7 +78,7 @@ def _warn_profile_fallback_once() -> None:
|
|||
try:
|
||||
fallback_home = _get_platform_default_hermes_home()
|
||||
active_path = fallback_home / "active_profile"
|
||||
active = active_path.read_text().strip() if active_path.exists() else ""
|
||||
active = active_path.read_text(encoding="utf-8").strip() if active_path.exists() else ""
|
||||
except (UnicodeDecodeError, OSError):
|
||||
active = ""
|
||||
if active and active != "default":
|
||||
|
|
|
|||
|
|
@ -68,7 +68,7 @@ ASSETS_DIR = Path(__file__).resolve().parent.parent / "assets"
|
|||
|
||||
|
||||
def load_template(name: str) -> str:
|
||||
return (ASSETS_DIR / name).read_text()
|
||||
return (ASSETS_DIR / name).read_text(encoding="utf-8")
|
||||
|
||||
|
||||
PROFILE_NAME_RE = re.compile(r"^[a-z0-9][a-z0-9_-]{0,63}$")
|
||||
|
|
@ -471,7 +471,7 @@ def main():
|
|||
help="Write TEAM.md alongside (default: skipped)")
|
||||
args = ap.parse_args()
|
||||
|
||||
plan = json.loads(Path(args.plan_json).read_text())
|
||||
plan = json.loads(Path(args.plan_json).read_text(encoding="utf-8"))
|
||||
errors = validate_plan(plan)
|
||||
if errors:
|
||||
print("Plan validation failed:", file=sys.stderr)
|
||||
|
|
@ -483,15 +483,15 @@ def main():
|
|||
team = render_team_md(plan)
|
||||
setup = render_setup_sh(plan, brief, team)
|
||||
|
||||
Path(args.out).write_text(setup)
|
||||
Path(args.out).write_text(setup, encoding="utf-8")
|
||||
os.chmod(args.out, 0o755)
|
||||
print(f"Wrote {args.out}")
|
||||
|
||||
if args.brief_out:
|
||||
Path(args.brief_out).write_text(brief)
|
||||
Path(args.brief_out).write_text(brief, encoding="utf-8")
|
||||
print(f"Wrote {args.brief_out}")
|
||||
if args.team_out:
|
||||
Path(args.team_out).write_text(team)
|
||||
Path(args.team_out).write_text(team, encoding="utf-8")
|
||||
print(f"Wrote {args.team_out}")
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -141,7 +141,7 @@ def build_findings(
|
|||
|
||||
# 3. Timing-based findings.
|
||||
if timing_path and Path(timing_path).exists():
|
||||
timing = json.loads(Path(timing_path).read_text())
|
||||
timing = json.loads(Path(timing_path).read_text(encoding="utf-8"))
|
||||
for r in timing.get("results", []):
|
||||
if not r.get("significant"):
|
||||
continue
|
||||
|
|
@ -190,7 +190,7 @@ def build_findings(
|
|||
},
|
||||
"findings": findings,
|
||||
}
|
||||
Path(out_path).write_text(json.dumps(payload, indent=2))
|
||||
Path(out_path).write_text(json.dumps(payload, indent=2), encoding="utf-8")
|
||||
return payload
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -198,7 +198,7 @@ def analyze(
|
|||
"results": results,
|
||||
}
|
||||
|
||||
Path(out_path).write_text(json.dumps(payload, indent=2))
|
||||
Path(out_path).write_text(json.dumps(payload, indent=2), encoding="utf-8")
|
||||
return payload
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -86,7 +86,7 @@ def _is_cron_provider_dir(path: Path) -> bool:
|
|||
if not init_file.exists():
|
||||
return False
|
||||
try:
|
||||
source = init_file.read_text(errors="replace")[:8192]
|
||||
source = init_file.read_text(errors="replace", encoding="utf-8")[:8192]
|
||||
return "register_cron_scheduler" in source or "CronScheduler" in source
|
||||
except Exception:
|
||||
return False
|
||||
|
|
|
|||
|
|
@ -110,12 +110,12 @@ def load_tracked() -> List[Dict[str, Any]]:
|
|||
return []
|
||||
|
||||
try:
|
||||
return json.loads(tf.read_text())
|
||||
return json.loads(tf.read_text(encoding="utf-8"))
|
||||
except (json.JSONDecodeError, ValueError):
|
||||
bak = tf.with_suffix(".json.bak")
|
||||
if bak.exists():
|
||||
try:
|
||||
data = json.loads(bak.read_text())
|
||||
data = json.loads(bak.read_text(encoding="utf-8"))
|
||||
_log("WARN: tracked.json corrupted — restored from .bak")
|
||||
return data
|
||||
except Exception:
|
||||
|
|
@ -129,7 +129,7 @@ def save_tracked(tracked: List[Dict[str, Any]]) -> None:
|
|||
tf = get_tracked_file()
|
||||
tf.parent.mkdir(parents=True, exist_ok=True)
|
||||
tmp = tf.with_suffix(".json.tmp")
|
||||
tmp.write_text(json.dumps(tracked, indent=2))
|
||||
tmp.write_text(json.dumps(tracked, indent=2), encoding="utf-8")
|
||||
if tf.exists():
|
||||
shutil.copy2(tf, tf.with_suffix(".json.bak"))
|
||||
tmp.replace(tf)
|
||||
|
|
|
|||
|
|
@ -262,7 +262,7 @@ class RealtimeSpeaker:
|
|||
if not self.queue_path.exists():
|
||||
return []
|
||||
out: list[dict] = []
|
||||
for line in self.queue_path.read_text().splitlines():
|
||||
for line in self.queue_path.read_text(encoding="utf-8").splitlines():
|
||||
line = line.strip()
|
||||
if not line:
|
||||
continue
|
||||
|
|
@ -281,10 +281,10 @@ class RealtimeSpeaker:
|
|||
if not remaining:
|
||||
# Keep the file but empty — consumers may be watching for
|
||||
# new writes via mtime, and delete-then-recreate is a race.
|
||||
self.queue_path.write_text("")
|
||||
self.queue_path.write_text("", encoding="utf-8")
|
||||
return
|
||||
self.queue_path.write_text(
|
||||
"\n".join(json.dumps(e) for e in remaining) + "\n"
|
||||
"\n".join(json.dumps(e) for e in remaining) + "\n", encoding="utf-8"
|
||||
)
|
||||
|
||||
def _append_processed(self, entry: dict, result: dict) -> None:
|
||||
|
|
|
|||
|
|
@ -159,7 +159,7 @@ def load_state() -> Dict[str, Any]:
|
|||
if not path.exists():
|
||||
return {"unlocks": {}}
|
||||
try:
|
||||
return json.loads(path.read_text())
|
||||
return json.loads(path.read_text(encoding="utf-8"))
|
||||
except Exception:
|
||||
return {"unlocks": {}}
|
||||
|
||||
|
|
@ -167,7 +167,7 @@ def load_state() -> Dict[str, Any]:
|
|||
def save_state(state: Dict[str, Any]) -> None:
|
||||
path = state_path()
|
||||
path.parent.mkdir(parents=True, exist_ok=True)
|
||||
path.write_text(json.dumps(state, indent=2, sort_keys=True))
|
||||
path.write_text(json.dumps(state, indent=2, sort_keys=True), encoding="utf-8")
|
||||
|
||||
|
||||
def _json_safe(value: Any) -> Any:
|
||||
|
|
@ -185,7 +185,7 @@ def load_snapshot() -> Optional[Dict[str, Any]]:
|
|||
if not path.exists():
|
||||
return None
|
||||
try:
|
||||
data = json.loads(path.read_text())
|
||||
data = json.loads(path.read_text(encoding="utf-8"))
|
||||
if isinstance(data, dict):
|
||||
return data
|
||||
except Exception:
|
||||
|
|
@ -196,7 +196,7 @@ def load_snapshot() -> Optional[Dict[str, Any]]:
|
|||
def save_snapshot(data: Dict[str, Any]) -> None:
|
||||
path = snapshot_path()
|
||||
path.parent.mkdir(parents=True, exist_ok=True)
|
||||
path.write_text(json.dumps(_json_safe(data), indent=2, sort_keys=True))
|
||||
path.write_text(json.dumps(_json_safe(data), indent=2, sort_keys=True), encoding="utf-8")
|
||||
|
||||
|
||||
def load_checkpoint() -> Dict[str, Any]:
|
||||
|
|
@ -204,7 +204,7 @@ def load_checkpoint() -> Dict[str, Any]:
|
|||
if not path.exists():
|
||||
return {"schema_version": 1, "generated_at": 0, "sessions": {}}
|
||||
try:
|
||||
data = json.loads(path.read_text())
|
||||
data = json.loads(path.read_text(encoding="utf-8"))
|
||||
if isinstance(data, dict):
|
||||
data.setdefault("schema_version", 1)
|
||||
data.setdefault("generated_at", 0)
|
||||
|
|
@ -219,7 +219,7 @@ def load_checkpoint() -> Dict[str, Any]:
|
|||
def save_checkpoint(data: Dict[str, Any]) -> None:
|
||||
path = checkpoint_path()
|
||||
path.parent.mkdir(parents=True, exist_ok=True)
|
||||
path.write_text(json.dumps(_json_safe(data), indent=2, sort_keys=True))
|
||||
path.write_text(json.dumps(_json_safe(data), indent=2, sort_keys=True), encoding="utf-8")
|
||||
|
||||
|
||||
def session_fingerprint(meta: Dict[str, Any]) -> Dict[str, Any]:
|
||||
|
|
|
|||
|
|
@ -81,7 +81,7 @@ def _is_memory_provider_dir(path: Path) -> bool:
|
|||
if not init_file.exists():
|
||||
return False
|
||||
try:
|
||||
source = init_file.read_text(errors="replace")[:8192]
|
||||
source = init_file.read_text(errors="replace", encoding="utf-8")[:8192]
|
||||
return "register_memory_provider" in source or "MemoryProvider" in source
|
||||
except Exception:
|
||||
return False
|
||||
|
|
|
|||
|
|
@ -228,7 +228,7 @@ def _save_mem0_json(hermes_home: str, data: dict) -> None:
|
|||
except Exception:
|
||||
pass
|
||||
existing.update(data)
|
||||
config_path.write_text(json.dumps(existing, indent=2) + "\n")
|
||||
config_path.write_text(json.dumps(existing, indent=2) + "\n", encoding="utf-8")
|
||||
|
||||
|
||||
def _setup_platform(hermes_home: str, config: dict, flags: dict[str, str]) -> None:
|
||||
|
|
@ -248,7 +248,7 @@ def _setup_platform(hermes_home: str, config: dict, flags: dict[str, str]) -> No
|
|||
config_path = Path(hermes_home) / "mem0.json"
|
||||
if config_path.exists():
|
||||
try:
|
||||
existing_config = json.loads(config_path.read_text())
|
||||
existing_config = json.loads(config_path.read_text(encoding="utf-8"))
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
|
|
@ -369,7 +369,7 @@ def _setup_selfhosted(hermes_home: str, config: dict, flags: dict[str, str]) ->
|
|||
config_path = Path(hermes_home) / "mem0.json"
|
||||
if config_path.exists():
|
||||
try:
|
||||
existing_config = json.loads(config_path.read_text())
|
||||
existing_config = json.loads(config_path.read_text(encoding="utf-8"))
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
|
|
|
|||
|
|
@ -373,6 +373,34 @@ FOOTGUNS: list[Footgun] = [
|
|||
and _is_likely_subprocess_call(line)
|
||||
),
|
||||
),
|
||||
Footgun(
|
||||
name="bare Path.read_text()/write_text() without encoding=",
|
||||
# Match ``.read_text(`` / ``.write_text(`` when the same line does
|
||||
# not pass ``encoding=``. Multi-line calls where encoding= sits on
|
||||
# a later line are handled by the post_filter's lookahead-free
|
||||
# heuristic accepting a small false-negative rate — the AST guard
|
||||
# test in tests/gateway/test_gateway_utf8_encoding.py catches the
|
||||
# gateway/adapters exactly, and this rule catches the common
|
||||
# single-line form everywhere else.
|
||||
pattern=re.compile(r"\.(read_text|write_text)\s*\("),
|
||||
message=(
|
||||
"Path.read_text()/write_text() without encoding= uses "
|
||||
"locale.getpreferredencoding() — cp936/cp1252 on Windows — "
|
||||
"so UTF-8 content (config JSON, session state, skills) "
|
||||
"crashes with UnicodeDecodeError or writes mojibake. "
|
||||
"See issue #37423 and the #71014 / read_text campaign."
|
||||
),
|
||||
fix='path.read_text(encoding="utf-8") / path.write_text(data, encoding="utf-8")',
|
||||
post_filter=lambda m, line: (
|
||||
"encoding=" not in line
|
||||
and "encoding =" not in line
|
||||
and not _looks_like_string_literal(line, m)
|
||||
# Skip calls that continue onto the next line — the closing
|
||||
# paren isn't on this line, so encoding= may follow. AST-level
|
||||
# enforcement for those lives in the gateway guard test.
|
||||
and line.rstrip().endswith(")")
|
||||
),
|
||||
),
|
||||
]
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -181,7 +181,7 @@ def main() -> int:
|
|||
if any(skip.rstrip("/") in parts for skip in SKIP_DIRS):
|
||||
continue
|
||||
|
||||
content = py_file.read_text()
|
||||
content = py_file.read_text(encoding="utf-8")
|
||||
violations = find_subprocess_calls(content, rel)
|
||||
all_violations.extend(violations)
|
||||
|
||||
|
|
@ -205,7 +205,7 @@ def main() -> int:
|
|||
continue
|
||||
|
||||
try:
|
||||
content = py_file.read_text()
|
||||
content = py_file.read_text(encoding="utf-8")
|
||||
except Exception:
|
||||
continue
|
||||
violations = find_subprocess_calls(content, rel)
|
||||
|
|
|
|||
|
|
@ -419,7 +419,7 @@ def main() -> int:
|
|||
pending_jobs=pending,
|
||||
)
|
||||
|
||||
args.output.write_text(body)
|
||||
args.output.write_text(body, encoding="utf-8")
|
||||
print(f"Wrote {len(body)} chars to {args.output}")
|
||||
return 0
|
||||
|
||||
|
|
|
|||
|
|
@ -30,7 +30,7 @@ def _load_json(path: Path | None) -> list[dict]:
|
|||
if path is None or not path.exists() or path.stat().st_size == 0:
|
||||
return []
|
||||
try:
|
||||
data = json.loads(path.read_text())
|
||||
data = json.loads(path.read_text(encoding="utf-8"))
|
||||
except json.JSONDecodeError as exc:
|
||||
print(f"warning: could not parse {path}: {exc}", file=sys.stderr)
|
||||
return []
|
||||
|
|
@ -197,7 +197,7 @@ def main() -> int:
|
|||
|
||||
summary = "\n".join(buf)
|
||||
if args.output:
|
||||
args.output.write_text(summary)
|
||||
args.output.write_text(summary, encoding="utf-8")
|
||||
else:
|
||||
print(summary)
|
||||
return 0
|
||||
|
|
|
|||
|
|
@ -516,7 +516,7 @@ def main() -> int:
|
|||
if not path.exists():
|
||||
print(f"\n⚠ no baseline at {path} — run with --save {args.compare} first")
|
||||
else:
|
||||
before = json.loads(path.read_text())
|
||||
before = json.loads(path.read_text(encoding="utf-8"))
|
||||
print(f"\n═══ A/B diff vs /tmp/perf-{args.compare}.json ═══")
|
||||
print(format_diff(before, metrics))
|
||||
|
||||
|
|
|
|||
|
|
@ -2138,7 +2138,7 @@ def next_available_tag(base_tag: str) -> tuple[str, str]:
|
|||
|
||||
def get_current_version():
|
||||
"""Read current semver from __init__.py."""
|
||||
content = VERSION_FILE.read_text()
|
||||
content = VERSION_FILE.read_text(encoding="utf-8")
|
||||
match = re.search(r'__version__\s*=\s*"([^"]+)"', content)
|
||||
return match.group(1) if match else "0.0.0"
|
||||
|
||||
|
|
@ -2168,7 +2168,7 @@ def bump_version(current: str, part: str) -> str:
|
|||
def update_version_files(semver: str, calver_date: str):
|
||||
"""Update version strings in source files."""
|
||||
# Update __init__.py
|
||||
content = VERSION_FILE.read_text()
|
||||
content = VERSION_FILE.read_text(encoding="utf-8")
|
||||
content = re.sub(
|
||||
r'__version__\s*=\s*"[^"]+"',
|
||||
f'__version__ = "{semver}"',
|
||||
|
|
@ -2179,17 +2179,17 @@ def update_version_files(semver: str, calver_date: str):
|
|||
f'__release_date__ = "{calver_date}"',
|
||||
content,
|
||||
)
|
||||
VERSION_FILE.write_text(content)
|
||||
VERSION_FILE.write_text(content, encoding="utf-8")
|
||||
|
||||
# Update pyproject.toml
|
||||
pyproject = PYPROJECT_FILE.read_text()
|
||||
pyproject = PYPROJECT_FILE.read_text(encoding="utf-8")
|
||||
pyproject = re.sub(
|
||||
r'^version\s*=\s*"[^"]+"',
|
||||
f'version = "{semver}"',
|
||||
pyproject,
|
||||
flags=re.MULTILINE,
|
||||
)
|
||||
PYPROJECT_FILE.write_text(pyproject)
|
||||
PYPROJECT_FILE.write_text(pyproject, encoding="utf-8")
|
||||
|
||||
# Keep the desktop Electron app's package.json version in lockstep with the
|
||||
# Python package version. The desktop About panel reads the live Hermes
|
||||
|
|
|
|||
|
|
@ -533,7 +533,7 @@ def _load_durations(repo_root: Path) -> dict[str, float]:
|
|||
if not path.is_file():
|
||||
return {}
|
||||
try:
|
||||
return json.loads(path.read_text())
|
||||
return json.loads(path.read_text(encoding="utf-8"))
|
||||
except (json.JSONDecodeError, OSError) as e:
|
||||
print("[ERROR] Failed to load json durations file! {e}")
|
||||
return {}
|
||||
|
|
@ -555,7 +555,7 @@ def _save_durations(
|
|||
key = _format_file(f, repo_root)
|
||||
data[key] = round(t, 3)
|
||||
path = repo_root / _DURATIONS_FILE
|
||||
path.write_text(json.dumps(data, indent=2, sort_keys=True) + "\n")
|
||||
path.write_text(json.dumps(data, indent=2, sort_keys=True) + "\n", encoding="utf-8")
|
||||
|
||||
|
||||
def _compute_lpt_slices(
|
||||
|
|
|
|||
|
|
@ -158,7 +158,7 @@ def main(argv: list[str] | None = None) -> int:
|
|||
sources: dict[str, str] = {}
|
||||
if args.models_from_file:
|
||||
try:
|
||||
sources = json.loads(Path(args.models_from_file).read_text())
|
||||
sources = json.loads(Path(args.models_from_file).read_text(encoding="utf-8"))
|
||||
except (OSError, json.JSONDecodeError) as e:
|
||||
log(f"Could not read --models-from-file: {e}")
|
||||
|
||||
|
|
|
|||
|
|
@ -303,7 +303,7 @@ def main(argv: list[str] | None = None) -> int:
|
|||
out = json.dumps(schema, indent=2, default=str)
|
||||
|
||||
if args.output:
|
||||
Path(args.output).write_text(out)
|
||||
Path(args.output).write_text(out, encoding="utf-8")
|
||||
print(f"Schema written to {args.output}", file=sys.stderr)
|
||||
else:
|
||||
print(out)
|
||||
|
|
|
|||
|
|
@ -620,7 +620,7 @@ def main(argv: list[str] | None = None) -> int:
|
|||
args_str = args.args
|
||||
if args_str.startswith("@"):
|
||||
try:
|
||||
args_str = Path(args_str[1:]).read_text()
|
||||
args_str = Path(args_str[1:]).read_text(encoding="utf-8")
|
||||
except OSError as e:
|
||||
emit_json({"error": f"Cannot read args file: {e}"})
|
||||
return 1
|
||||
|
|
|
|||
|
|
@ -22,17 +22,17 @@ sys.path.insert(0, str(SCRIPTS))
|
|||
|
||||
@pytest.fixture
|
||||
def sd15_workflow() -> dict:
|
||||
return json.loads((WORKFLOWS / "sd15_txt2img.json").read_text())
|
||||
return json.loads((WORKFLOWS / "sd15_txt2img.json").read_text(encoding="utf-8"))
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def flux_workflow() -> dict:
|
||||
return json.loads((WORKFLOWS / "flux_dev_txt2img.json").read_text())
|
||||
return json.loads((WORKFLOWS / "flux_dev_txt2img.json").read_text(encoding="utf-8"))
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def video_workflow() -> dict:
|
||||
return json.loads((WORKFLOWS / "wan_video_t2v.json").read_text())
|
||||
return json.loads((WORKFLOWS / "wan_video_t2v.json").read_text(encoding="utf-8"))
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
|
|
|
|||
|
|
@ -436,7 +436,7 @@ class TestVideoWorkflow:
|
|||
|
||||
def test_animatediff_workflow(self, workflows_dir):
|
||||
import json
|
||||
wf = json.loads((workflows_dir / "animatediff_video.json").read_text())
|
||||
wf = json.loads((workflows_dir / "animatediff_video.json").read_text(encoding="utf-8"))
|
||||
assert looks_like_video_workflow(wf) is True
|
||||
|
||||
def test_wan_workflow(self, video_workflow):
|
||||
|
|
|
|||
|
|
@ -16,20 +16,20 @@ from run_workflow import (
|
|||
class TestParseInputImageArg:
|
||||
def test_with_name(self, tmp_path):
|
||||
f = tmp_path / "x.png"
|
||||
f.write_text("x")
|
||||
f.write_text("x", encoding="utf-8")
|
||||
n, p = parse_input_image_arg(f"image={f}")
|
||||
assert n == "image"
|
||||
assert p == f
|
||||
|
||||
def test_without_name_defaults(self, tmp_path):
|
||||
f = tmp_path / "x.png"
|
||||
f.write_text("x")
|
||||
f.write_text("x", encoding="utf-8")
|
||||
n, p = parse_input_image_arg(str(f))
|
||||
assert n == "image"
|
||||
|
||||
def test_custom_name(self, tmp_path):
|
||||
f = tmp_path / "x.png"
|
||||
f.write_text("x")
|
||||
f.write_text("x", encoding="utf-8")
|
||||
n, p = parse_input_image_arg(f"mask_image={f}")
|
||||
assert n == "mask_image"
|
||||
|
||||
|
|
|
|||
|
|
@ -92,7 +92,7 @@ def _setup_libreoffice_macro() -> bool:
|
|||
macro_dir = Path(MACRO_DIR)
|
||||
macro_file = macro_dir / "Module1.xba"
|
||||
|
||||
if macro_file.exists() and "AcceptAllTrackedChanges" in macro_file.read_text():
|
||||
if macro_file.exists() and "AcceptAllTrackedChanges" in macro_file.read_text(encoding="utf-8"):
|
||||
return True
|
||||
|
||||
if not macro_dir.exists():
|
||||
|
|
@ -111,7 +111,7 @@ def _setup_libreoffice_macro() -> bool:
|
|||
macro_dir.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
try:
|
||||
macro_file.write_text(ACCEPT_CHANGES_MACRO)
|
||||
macro_file.write_text(ACCEPT_CHANGES_MACRO, encoding="utf-8")
|
||||
return True
|
||||
except Exception as e:
|
||||
logger.warning(f"Failed to setup LibreOffice macro: {e}")
|
||||
|
|
|
|||
|
|
@ -64,7 +64,7 @@ def _ensure_shim() -> Path:
|
|||
return _SHIM_SO
|
||||
|
||||
src = Path(tempfile.gettempdir()) / "lo_socket_shim.c"
|
||||
src.write_text(_SHIM_SOURCE)
|
||||
src.write_text(_SHIM_SOURCE, encoding="utf-8")
|
||||
subprocess.run(
|
||||
["gcc", "-shared", "-fPIC", "-o", str(_SHIM_SO), str(src), "-ldl"],
|
||||
check=True,
|
||||
|
|
|
|||
|
|
@ -70,7 +70,7 @@ def _ensure_authenticated():
|
|||
|
||||
def _stored_token_scopes() -> list[str]:
|
||||
try:
|
||||
data = json.loads(TOKEN_PATH.read_text())
|
||||
data = json.loads(TOKEN_PATH.read_text(encoding="utf-8"))
|
||||
except Exception:
|
||||
return list(SCOPES)
|
||||
scopes = data.get("scopes")
|
||||
|
|
@ -192,7 +192,7 @@ def get_credentials():
|
|||
json.dumps(
|
||||
_normalize_authorized_user_payload(json.loads(creds.to_json())),
|
||||
indent=2,
|
||||
)
|
||||
), encoding="utf-8"
|
||||
)
|
||||
if not creds.valid:
|
||||
print("Token is invalid. Re-run setup.", file=sys.stderr)
|
||||
|
|
|
|||
|
|
@ -69,7 +69,7 @@ def refresh_token(token_data: dict) -> dict:
|
|||
).isoformat()
|
||||
|
||||
get_token_path().write_text(
|
||||
json.dumps(_normalize_authorized_user_payload(token_data), indent=2)
|
||||
json.dumps(_normalize_authorized_user_payload(token_data), indent=2), encoding="utf-8"
|
||||
)
|
||||
return token_data
|
||||
|
||||
|
|
@ -81,7 +81,7 @@ def get_valid_token() -> str:
|
|||
print("ERROR: No Google token found. Run setup.py --auth-url first.", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
token_data = json.loads(token_path.read_text())
|
||||
token_data = json.loads(token_path.read_text(encoding="utf-8"))
|
||||
|
||||
expiry = token_data.get("expiry", "")
|
||||
if expiry:
|
||||
|
|
|
|||
|
|
@ -71,7 +71,7 @@ def _normalize_authorized_user_payload(payload: dict) -> dict:
|
|||
|
||||
def _load_token_payload(path: Path = TOKEN_PATH) -> dict:
|
||||
try:
|
||||
return json.loads(path.read_text())
|
||||
return json.loads(path.read_text(encoding="utf-8"))
|
||||
except Exception:
|
||||
return {}
|
||||
|
||||
|
|
@ -220,7 +220,7 @@ def check_auth(quiet: bool = False):
|
|||
json.dumps(
|
||||
_normalize_authorized_user_payload(json.loads(creds.to_json())),
|
||||
indent=2,
|
||||
)
|
||||
), encoding="utf-8"
|
||||
)
|
||||
missing_scopes = _missing_scopes_from_payload(_load_token_payload(TOKEN_PATH))
|
||||
if missing_scopes:
|
||||
|
|
@ -260,7 +260,7 @@ def store_client_secret(path: str):
|
|||
sys.exit(1)
|
||||
|
||||
try:
|
||||
data = json.loads(src.read_text())
|
||||
data = json.loads(src.read_text(encoding="utf-8"))
|
||||
except json.JSONDecodeError:
|
||||
print("ERROR: File is not valid JSON.")
|
||||
sys.exit(1)
|
||||
|
|
@ -270,7 +270,7 @@ def store_client_secret(path: str):
|
|||
print("Download the correct file from: https://console.cloud.google.com/apis/credentials")
|
||||
sys.exit(1)
|
||||
|
||||
CLIENT_SECRET_PATH.write_text(json.dumps(data, indent=2))
|
||||
CLIENT_SECRET_PATH.write_text(json.dumps(data, indent=2), encoding="utf-8")
|
||||
print(f"OK: Client secret saved to {CLIENT_SECRET_PATH}")
|
||||
|
||||
|
||||
|
|
@ -284,7 +284,7 @@ def _save_pending_auth(*, state: str, code_verifier: str):
|
|||
"redirect_uri": REDIRECT_URI,
|
||||
},
|
||||
indent=2,
|
||||
)
|
||||
), encoding="utf-8"
|
||||
)
|
||||
|
||||
|
||||
|
|
@ -295,7 +295,7 @@ def _load_pending_auth() -> dict:
|
|||
sys.exit(1)
|
||||
|
||||
try:
|
||||
data = json.loads(PENDING_AUTH_PATH.read_text())
|
||||
data = json.loads(PENDING_AUTH_PATH.read_text(encoding="utf-8"))
|
||||
except Exception as e:
|
||||
print(f"ERROR: Could not read pending OAuth session: {e}")
|
||||
print("Run --auth-url again to start a fresh OAuth session.")
|
||||
|
|
@ -410,7 +410,7 @@ def exchange_auth_code(code: str):
|
|||
print(f"WARNING: Token missing some Google Workspace scopes: {', '.join(missing_scopes)}")
|
||||
print("Some services may not be available.")
|
||||
|
||||
TOKEN_PATH.write_text(json.dumps(token_payload, indent=2))
|
||||
TOKEN_PATH.write_text(json.dumps(token_payload, indent=2), encoding="utf-8")
|
||||
PENDING_AUTH_PATH.unlink(missing_ok=True)
|
||||
print(f"OK: Authenticated. Token saved to {TOKEN_PATH}")
|
||||
print(f"Profile-scoped token location: {display_hermes_home()}/google_token.json")
|
||||
|
|
|
|||
|
|
@ -64,7 +64,7 @@ def _ensure_shim() -> Path:
|
|||
return _SHIM_SO
|
||||
|
||||
src = Path(tempfile.gettempdir()) / "lo_socket_shim.c"
|
||||
src.write_text(_SHIM_SOURCE)
|
||||
src.write_text(_SHIM_SOURCE, encoding="utf-8")
|
||||
subprocess.run(
|
||||
["gcc", "-shared", "-fPIC", "-o", str(_SHIM_SO), str(src), "-ldl"],
|
||||
check=True,
|
||||
|
|
|
|||
|
|
@ -64,7 +64,7 @@ def _ensure_shim() -> Path:
|
|||
return _SHIM_SO
|
||||
|
||||
src = Path(tempfile.gettempdir()) / "lo_socket_shim.c"
|
||||
src.write_text(_SHIM_SOURCE)
|
||||
src.write_text(_SHIM_SOURCE, encoding="utf-8")
|
||||
subprocess.run(
|
||||
["gcc", "-shared", "-fPIC", "-o", str(_SHIM_SO), str(src), "-ldl"],
|
||||
check=True,
|
||||
|
|
|
|||
|
|
@ -71,7 +71,7 @@ def setup_libreoffice_macro(profile_dir: Path, timeout=30):
|
|||
return None, "LibreOffice did not create a usable profile; formulas were NOT recalculated"
|
||||
|
||||
try:
|
||||
(macro_dir / MACRO_FILENAME).write_text(RECALCULATE_MACRO)
|
||||
(macro_dir / MACRO_FILENAME).write_text(RECALCULATE_MACRO, encoding="utf-8")
|
||||
except OSError as e:
|
||||
return None, f"Could not install the recalculation macro: {e}"
|
||||
|
||||
|
|
|
|||
|
|
@ -37,7 +37,7 @@ def _read_nous_provider_state() -> Optional[dict]:
|
|||
path = auth_json_path()
|
||||
if not path.is_file():
|
||||
return None
|
||||
data = json.loads(path.read_text())
|
||||
data = json.loads(path.read_text(encoding="utf-8"))
|
||||
providers = data.get("providers", {})
|
||||
if not isinstance(providers, dict):
|
||||
return None
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue