Add YAPF style + ignore, and format tracked Python files

This commit is contained in:
2025-12-29 18:42:02 -08:00
parent c019c00aed
commit 507946a3e4
108 changed files with 11664 additions and 6494 deletions

View File

@@ -50,7 +50,11 @@ except Exception:
def detach_kwargs_for_platform():
kwargs = {}
if os.name == "nt":
CREATE_NEW_PROCESS_GROUP = getattr(subprocess, "CREATE_NEW_PROCESS_GROUP", 0)
CREATE_NEW_PROCESS_GROUP = getattr(
subprocess,
"CREATE_NEW_PROCESS_GROUP",
0
)
DETACHED_PROCESS = getattr(subprocess, "DETACHED_PROCESS", 0)
flags = CREATE_NEW_PROCESS_GROUP | DETACHED_PROCESS
if flags:
@@ -70,7 +74,11 @@ def find_git_executable() -> Optional[str]:
# Quick sanity check
try:
subprocess.run(
[git, "--version"], check=True, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL
[git,
"--version"],
check=True,
stdout=subprocess.DEVNULL,
stderr=subprocess.DEVNULL
)
return git
except Exception:
@@ -88,7 +96,11 @@ def is_git_repo(path: Path) -> bool:
return False
try:
subprocess.run(
[git, "-C", str(path), "rev-parse", "--is-inside-work-tree"],
[git,
"-C",
str(path),
"rev-parse",
"--is-inside-work-tree"],
check=True,
stdout=subprocess.DEVNULL,
stderr=subprocess.DEVNULL,
@@ -99,7 +111,11 @@ def is_git_repo(path: Path) -> bool:
def run_git_clone(
git: str, repo: str, dest: Path, branch: Optional[str] = None, depth: Optional[int] = None
git: str,
repo: str,
dest: Path,
branch: Optional[str] = None,
depth: Optional[int] = None
) -> None:
# Build git clone with options before the repository argument. Support shallow clones
# via --depth when requested.
@@ -113,7 +129,12 @@ def run_git_clone(
if branch:
cmd += ["--branch", branch]
cmd += [repo, str(dest)]
logging.info("Cloning: %s -> %s (depth=%s)", repo, dest, str(depth) if depth else "full")
logging.info(
"Cloning: %s -> %s (depth=%s)",
repo,
dest,
str(depth) if depth else "full"
)
subprocess.run(cmd, check=True)
@@ -123,7 +144,11 @@ def run_git_pull(git: str, dest: Path) -> None:
def download_and_extract_zip(
repo_url: str, dest: Path, branch_candidates: Tuple[str, ...] = ("main", "master")
repo_url: str,
dest: Path,
branch_candidates: Tuple[str,
...] = ("main",
"master")
) -> None:
"""Download the GitHub repo zip and extract it into dest.
@@ -181,7 +206,10 @@ def download_and_extract_zip(
target.unlink()
shutil.move(str(entry), str(dest))
logging.info(
"Downloaded and extracted %s (branch: %s) into %s", repo_url, branch, dest
"Downloaded and extracted %s (branch: %s) into %s",
repo_url,
branch,
dest
)
return
except Exception as exc:
@@ -277,9 +305,9 @@ def maybe_reexec_under_project_venv(root: Path, disable: bool = False) -> None:
script_path = Path(sys.argv[0]).resolve()
except Exception:
script_path = None
args = [str(py), str(script_path) if script_path is not None else sys.argv[0]] + sys.argv[
1:
]
args = [str(py),
str(script_path) if script_path is not None else sys.argv[0]
] + sys.argv[1:]
logging.debug("Exec args: %s", args)
os.execvpe(str(py), args, env)
except Exception as exc:
@@ -325,12 +353,21 @@ def fix_permissions_windows(path: Path, user: Optional[str] = None) -> bool:
except Exception:
user = getpass.getuser()
logging.info("Attempting Windows ownership/ACL fix for %s (owner=%s)", path, user)
logging.info(
"Attempting Windows ownership/ACL fix for %s (owner=%s)",
path,
user
)
# Try to take ownership (best-effort)
try:
subprocess.run(
["takeown", "/F", str(path), "/R", "/D", "Y"],
["takeown",
"/F",
str(path),
"/R",
"/D",
"Y"],
check=False,
stdout=subprocess.DEVNULL,
stderr=subprocess.DEVNULL,
@@ -341,14 +378,28 @@ def fix_permissions_windows(path: Path, user: Optional[str] = None) -> bool:
rc_setowner = 1
rc_grant = 1
try:
out = subprocess.run(["icacls", str(path), "/setowner", user, "/T", "/C"], check=False)
out = subprocess.run(
["icacls",
str(path),
"/setowner",
user,
"/T",
"/C"],
check=False
)
rc_setowner = int(out.returncode)
except Exception:
rc_setowner = 1
try:
out = subprocess.run(
["icacls", str(path), "/grant", f"{user}:(OI)(CI)F", "/T", "/C"], check=False
["icacls",
str(path),
"/grant",
f"{user}:(OI)(CI)F",
"/T",
"/C"],
check=False
)
rc_grant = int(out.returncode)
except Exception:
@@ -368,7 +419,9 @@ def fix_permissions_windows(path: Path, user: Optional[str] = None) -> bool:
def fix_permissions_unix(
path: Path, user: Optional[str] = None, group: Optional[str] = None
path: Path,
user: Optional[str] = None,
group: Optional[str] = None
) -> bool:
"""Attempt to chown/chmod recursively for a Unix-like system.
@@ -398,7 +451,13 @@ def fix_permissions_unix(
)
try:
subprocess.run(["chown", "-R", f"{user}:{group or pw.pw_gid}", str(path)], check=True)
subprocess.run(
["chown",
"-R",
f"{user}:{group or pw.pw_gid}",
str(path)],
check=True
)
except Exception:
# Best-effort fallback: chown/chmod individual entries
for root_dir, dirs, files in os.walk(path):
@@ -426,14 +485,20 @@ def fix_permissions_unix(
except Exception:
pass
logging.info("Unix permission fix attempted (some changes may require root privilege).")
logging.info(
"Unix permission fix attempted (some changes may require root privilege)."
)
return True
except Exception as exc:
logging.debug("Unix fix-permissions error: %s", exc)
return False
def fix_permissions(path: Path, user: Optional[str] = None, group: Optional[str] = None) -> bool:
def fix_permissions(
path: Path,
user: Optional[str] = None,
group: Optional[str] = None
) -> bool:
try:
if os.name == "nt":
return fix_permissions_windows(path, user=user)
@@ -554,9 +619,8 @@ def open_in_editor(path: Path) -> bool:
pass
# Linux: use xdg-open only if a display is available and xdg-open exists
if shutil.which("xdg-open") and (
os.environ.get("DISPLAY") or os.environ.get("WAYLAND_DISPLAY")
):
if shutil.which("xdg-open") and (os.environ.get("DISPLAY")
or os.environ.get("WAYLAND_DISPLAY")):
try:
subprocess.run(["xdg-open", str(path)], check=False)
logging.info("Opened %s with default application", path)
@@ -565,7 +629,8 @@ def open_in_editor(path: Path) -> bool:
pass
logging.debug(
"No available method to open %s automatically (headless or no opener installed)", path
"No available method to open %s automatically (headless or no opener installed)",
path
)
return False
except Exception as exc:
@@ -574,12 +639,15 @@ def open_in_editor(path: Path) -> bool:
def main(argv: Optional[list[str]] = None) -> int:
parser = argparse.ArgumentParser(description="Clone Hydrus into a 'hydrusnetwork' directory.")
parser = argparse.ArgumentParser(
description="Clone Hydrus into a 'hydrusnetwork' directory."
)
parser.add_argument(
"--root",
"-r",
default=".",
help="Root folder to create the hydrusnetwork directory in (default: current working directory)",
help=
"Root folder to create the hydrusnetwork directory in (default: current working directory)",
)
parser.add_argument(
"--dest-name",
@@ -588,7 +656,9 @@ def main(argv: Optional[list[str]] = None) -> int:
help="Name of the destination folder (default: hydrusnetwork)",
)
parser.add_argument(
"--repo", default="https://github.com/hydrusnetwork/hydrus", help="Repository URL to clone"
"--repo",
default="https://github.com/hydrusnetwork/hydrus",
help="Repository URL to clone"
)
parser.add_argument(
"--update",
@@ -602,31 +672,40 @@ def main(argv: Optional[list[str]] = None) -> int:
help="Remove existing destination directory before cloning",
)
parser.add_argument(
"--branch", "-b", default=None, help="Branch to clone (passed to git clone --branch)."
"--branch",
"-b",
default=None,
help="Branch to clone (passed to git clone --branch)."
)
parser.add_argument(
"--depth",
type=int,
default=1,
help="If set, pass --depth to git clone (default: 1 for a shallow clone). Use --full to perform a full clone instead.",
help=
"If set, pass --depth to git clone (default: 1 for a shallow clone). Use --full to perform a full clone instead.",
)
parser.add_argument(
"--full", action="store_true", help="Perform a full clone (no --depth passed to git clone)"
"--full",
action="store_true",
help="Perform a full clone (no --depth passed to git clone)"
)
parser.add_argument(
"--git",
action="store_true",
help="Use git clone instead of fetching repository ZIP (opt-in). Default: fetch ZIP (smaller).",
help=
"Use git clone instead of fetching repository ZIP (opt-in). Default: fetch ZIP (smaller).",
)
parser.add_argument(
"--no-fallback",
action="store_true",
help="If set, do not attempt to download ZIP when git is missing (only relevant with --git)",
help=
"If set, do not attempt to download ZIP when git is missing (only relevant with --git)",
)
parser.add_argument(
"--fix-permissions",
action="store_true",
help="Fix ownership/permissions on the obtained repo (OS-aware). Requires elevated privileges for some actions.",
help=
"Fix ownership/permissions on the obtained repo (OS-aware). Requires elevated privileges for some actions.",
)
parser.add_argument(
"--fix-permissions-user",
@@ -641,7 +720,8 @@ def main(argv: Optional[list[str]] = None) -> int:
parser.add_argument(
"--no-venv",
action="store_true",
help="Do not create a venv inside the cloned repo (default: create a .venv folder)",
help=
"Do not create a venv inside the cloned repo (default: create a .venv folder)",
)
parser.add_argument(
"--venv-name",
@@ -649,7 +729,9 @@ def main(argv: Optional[list[str]] = None) -> int:
help="Name of the venv directory to create inside the repo (default: .venv)",
)
parser.add_argument(
"--recreate-venv", action="store_true", help="Remove existing venv and create a fresh one"
"--recreate-venv",
action="store_true",
help="Remove existing venv and create a fresh one"
)
# By default install dependencies into the created venv; use --no-install-deps to opt out
group_install = parser.add_mutually_exclusive_group()
@@ -657,7 +739,8 @@ def main(argv: Optional[list[str]] = None) -> int:
"--install-deps",
dest="install_deps",
action="store_true",
help="Install dependencies from requirements.txt into the created venv (default).",
help=
"Install dependencies from requirements.txt into the created venv (default).",
)
group_install.add_argument(
"--no-install-deps",
@@ -669,17 +752,20 @@ def main(argv: Optional[list[str]] = None) -> int:
parser.add_argument(
"--reinstall-deps",
action="store_true",
help="If present, force re-install dependencies into the created venv using pip --force-reinstall.",
help=
"If present, force re-install dependencies into the created venv using pip --force-reinstall.",
)
parser.add_argument(
"--no-open-client",
action="store_true",
help="(ignored) installer no longer opens hydrus_client.py automatically; use the run_client helper to launch the client when ready.",
help=
"(ignored) installer no longer opens hydrus_client.py automatically; use the run_client helper to launch the client when ready.",
)
parser.add_argument(
"--run-client",
action="store_true",
help="Run hydrus_client.py using the repo-local venv's Python (if present). This runs the client in the foreground unless --run-client-detached is specified.",
help=
"Run hydrus_client.py using the repo-local venv's Python (if present). This runs the client in the foreground unless --run-client-detached is specified.",
)
parser.add_argument(
"--run-client-detached",
@@ -689,7 +775,8 @@ def main(argv: Optional[list[str]] = None) -> int:
parser.add_argument(
"--run-client-headless",
action="store_true",
help="If used with --run-client, attempt to run hydrus_client.py without showing the Qt GUI (best-effort)",
help=
"If used with --run-client, attempt to run hydrus_client.py without showing the Qt GUI (best-effort)",
)
parser.add_argument(
"--install-service",
@@ -759,7 +846,10 @@ def main(argv: Optional[list[str]] = None) -> int:
)
return 0
logging.info("Destination %s is already a git repository.", dest)
logging.info(
"Destination %s is already a git repository.",
dest
)
print("")
print("Select an action:")
print(
@@ -779,13 +869,21 @@ def main(argv: Optional[list[str]] = None) -> int:
if choice == "1":
# Install dependencies into the repository venv (create venv if needed)
try:
venv_dir = dest / str(getattr(args, "venv_name", ".venv"))
venv_dir = dest / str(
getattr(args,
"venv_name",
".venv")
)
if venv_dir.exists():
logging.info("Using existing venv at %s", venv_dir)
else:
logging.info("Creating venv at %s", venv_dir)
subprocess.run(
[sys.executable, "-m", "venv", str(venv_dir)], check=True
[sys.executable,
"-m",
"venv",
str(venv_dir)],
check=True
)
venv_py = get_python_in_venv(venv_dir)
except Exception as e:
@@ -793,24 +891,45 @@ def main(argv: Optional[list[str]] = None) -> int:
return 8
if not venv_py:
logging.error("Could not locate python in venv %s", venv_dir)
logging.error(
"Could not locate python in venv %s",
venv_dir
)
return 9
req = find_requirements(dest)
if not req:
logging.info(
"No requirements.txt found in %s; nothing to install.", dest
"No requirements.txt found in %s; nothing to install.",
dest
)
return 0
logging.info("Installing dependencies from %s into venv", req)
logging.info(
"Installing dependencies from %s into venv",
req
)
try:
subprocess.run(
[str(venv_py), "-m", "pip", "install", "--upgrade", "pip"],
[
str(venv_py),
"-m",
"pip",
"install",
"--upgrade",
"pip"
],
check=True,
)
subprocess.run(
[str(venv_py), "-m", "pip", "install", "-r", str(req)],
[
str(venv_py),
"-m",
"pip",
"install",
"-r",
str(req)
],
cwd=str(dest),
check=True,
)
@@ -822,7 +941,9 @@ def main(argv: Optional[list[str]] = None) -> int:
# Post-install verification
pkgs = parse_requirements_file(req)
if pkgs:
logging.info("Verifying installed packages inside the venv...")
logging.info(
"Verifying installed packages inside the venv..."
)
any_missing = False
import_map = {
"pyyaml": "yaml",
@@ -843,7 +964,9 @@ def main(argv: Optional[list[str]] = None) -> int:
mod = import_map.get(pkg, pkg)
try:
subprocess.run(
[str(venv_py), "-c", f"import {mod}"],
[str(venv_py),
"-c",
f"import {mod}"],
check=True,
stdout=subprocess.DEVNULL,
stderr=subprocess.DEVNULL,
@@ -869,7 +992,9 @@ def main(argv: Optional[list[str]] = None) -> int:
elif choice == "2":
if not git:
logging.error("Git not found; cannot --update without git")
logging.error(
"Git not found; cannot --update without git"
)
return 2
try:
run_git_pull(git, dest)
@@ -882,17 +1007,29 @@ def main(argv: Optional[list[str]] = None) -> int:
elif choice == "3":
# Install a user-level service to start the hydrus client on boot
try:
venv_dir = dest / str(getattr(args, "venv_name", ".venv"))
venv_dir = dest / str(
getattr(args,
"venv_name",
".venv")
)
if not venv_dir.exists():
logging.info(
"Creating venv at %s to perform service install", venv_dir
"Creating venv at %s to perform service install",
venv_dir
)
subprocess.run(
[sys.executable, "-m", "venv", str(venv_dir)], check=True
[sys.executable,
"-m",
"venv",
str(venv_dir)],
check=True
)
venv_py = get_python_in_venv(venv_dir)
except Exception as e:
logging.error("Failed to prepare venv for service install: %s", e)
logging.error(
"Failed to prepare venv for service install: %s",
e
)
return 8
if not venv_py:
@@ -986,7 +1123,13 @@ def main(argv: Optional[list[str]] = None) -> int:
try:
# Default behavior when using git: shallow clone (depth=1) unless --full specified.
depth_to_use = None if getattr(args, "full", False) else args.depth
run_git_clone(git, args.repo, dest, branch=args.branch, depth=depth_to_use)
run_git_clone(
git,
args.repo,
dest,
branch=args.branch,
depth=depth_to_use
)
logging.info("Repository cloned into %s", dest)
obtained = True
obtained_by = "git"
@@ -1023,7 +1166,13 @@ def main(argv: Optional[list[str]] = None) -> int:
if not venv_dir.exists():
logging.info("Creating venv at %s", venv_dir)
try:
subprocess.run([sys.executable, "-m", "venv", str(venv_dir)], check=True)
subprocess.run(
[sys.executable,
"-m",
"venv",
str(venv_dir)],
check=True
)
except subprocess.CalledProcessError as e:
logging.error("Failed to create venv: %s", e)
return 8
@@ -1038,17 +1187,30 @@ def main(argv: Optional[list[str]] = None) -> int:
logging.info("Venv ready: %s", venv_py)
# Optionally install or reinstall requirements.txt
if getattr(args, "install_deps", False) or getattr(args, "reinstall_deps", False):
if getattr(args,
"install_deps",
False) or getattr(args,
"reinstall_deps",
False):
req = find_requirements(dest)
if req and req.exists():
logging.info(
"Installing dependencies from %s into venv (reinstall=%s)",
req,
bool(getattr(args, "reinstall_deps", False)),
bool(getattr(args,
"reinstall_deps",
False)),
)
try:
subprocess.run(
[str(venv_py), "-m", "pip", "install", "--upgrade", "pip"],
[
str(venv_py),
"-m",
"pip",
"install",
"--upgrade",
"pip"
],
check=True,
)
if getattr(args, "reinstall_deps", False):
@@ -1068,7 +1230,14 @@ def main(argv: Optional[list[str]] = None) -> int:
)
else:
subprocess.run(
[str(venv_py), "-m", "pip", "install", "-r", str(req)],
[
str(venv_py),
"-m",
"pip",
"install",
"-r",
str(req)
],
cwd=str(dest),
check=True,
)
@@ -1080,7 +1249,9 @@ def main(argv: Optional[list[str]] = None) -> int:
# Post-install verification: ensure packages are visible inside the venv
pkgs = parse_requirements_file(req)
if pkgs:
logging.info("Verifying installed packages inside the venv...")
logging.info(
"Verifying installed packages inside the venv..."
)
any_missing = False
# Small mapping for known differences between package name and import name
import_map = {
@@ -1101,14 +1272,19 @@ def main(argv: Optional[list[str]] = None) -> int:
for pkg in pkgs:
try:
out = subprocess.run(
[str(venv_py), "-m", "pip", "show", pkg],
[str(venv_py),
"-m",
"pip",
"show",
pkg],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
text=True,
)
if out.returncode != 0 or not out.stdout.strip():
logging.warning(
"Package '%s' not found in venv (pip show failed).", pkg
"Package '%s' not found in venv (pip show failed).",
pkg
)
any_missing = True
continue
@@ -1116,7 +1292,11 @@ def main(argv: Optional[list[str]] = None) -> int:
import_name = import_map.get(pkg, pkg)
try:
subprocess.run(
[str(venv_py), "-c", f"import {import_name}"],
[
str(venv_py),
"-c",
f"import {import_name}"
],
check=True,
stdout=subprocess.DEVNULL,
stderr=subprocess.DEVNULL,
@@ -1129,7 +1309,11 @@ def main(argv: Optional[list[str]] = None) -> int:
)
any_missing = True
except Exception as exc:
logging.debug("Verification error for package %s: %s", pkg, exc)
logging.debug(
"Verification error for package %s: %s",
pkg,
exc
)
any_missing = True
if any_missing:
@@ -1182,7 +1366,10 @@ def main(argv: Optional[list[str]] = None) -> int:
logging.info(" source %s/bin/activate", venv_dir)
# Optionally open/run hydrus_client.py in the repo for convenience (open by default if present).
client_candidates = [dest / "hydrus_client.py", dest / "client" / "hydrus_client.py"]
client_candidates = [
dest / "hydrus_client.py",
dest / "client" / "hydrus_client.py"
]
client_found = None
for p in client_candidates:
if p.exists():
@@ -1197,12 +1384,18 @@ def main(argv: Optional[list[str]] = None) -> int:
if cand.exists():
run_client_script = cand
break
if getattr(args, "install_service", False) or getattr(args, "uninstall_service", False):
if getattr(args,
"install_service",
False) or getattr(args,
"uninstall_service",
False):
if not venv_py:
venv_dir = dest / str(getattr(args, "venv_name", ".venv"))
venv_py = get_python_in_venv(venv_dir)
if not venv_py:
logging.error("Could not locate python in repo venv; cannot manage service.")
logging.error(
"Could not locate python in repo venv; cannot manage service."
)
else:
if getattr(args, "install_service", False):
if run_client_script.exists():
@@ -1224,7 +1417,11 @@ def main(argv: Optional[list[str]] = None) -> int:
else:
if install_service_auto:
ok = install_service_auto(
args.service_name, dest, venv_py, headless=True, detached=True
args.service_name,
dest,
venv_py,
headless=True,
detached=True
)
if ok:
logging.info("Service installed (user-level).")
@@ -1253,7 +1450,11 @@ def main(argv: Optional[list[str]] = None) -> int:
logging.error("Service uninstall failed: %s", e)
else:
if uninstall_service_auto:
ok = uninstall_service_auto(args.service_name, dest, venv_py)
ok = uninstall_service_auto(
args.service_name,
dest,
venv_py
)
if ok:
logging.info("Service removed.")
else:
@@ -1294,17 +1495,27 @@ def main(argv: Optional[list[str]] = None) -> int:
if getattr(args, "run_client_detached", False):
cmd.append("--detached")
logging.info("Running hydrus client via helper: %s", cmd)
logging.info(
"Running hydrus client via helper: %s",
cmd
)
try:
if getattr(args, "run_client_detached", False):
kwargs = detach_kwargs_for_platform()
kwargs.update({"cwd": str(dest)})
kwargs.update({
"cwd": str(dest)
})
subprocess.Popen(cmd, **kwargs)
logging.info("Hydrus client launched (detached).")
logging.info(
"Hydrus client launched (detached)."
)
else:
subprocess.run(cmd, cwd=str(dest))
except subprocess.CalledProcessError as e:
logging.error("run_client.py exited non-zero: %s", e)
logging.error(
"run_client.py exited non-zero: %s",
e
)
else:
# Fallback: call the client directly; support headless by setting
# QT_QPA_PLATFORM or using xvfb-run on Linux.
@@ -1327,23 +1538,34 @@ def main(argv: Optional[list[str]] = None) -> int:
)
logging.info(
"Running hydrus client with %s: %s", venv_py, client_found
"Running hydrus client with %s: %s",
venv_py,
client_found
)
if getattr(args, "run_client_detached", False):
try:
kwargs = detach_kwargs_for_platform()
kwargs.update({"cwd": str(dest), "env": env})
kwargs.update({
"cwd": str(dest),
"env": env
})
subprocess.Popen(cmd, **kwargs)
logging.info("Hydrus client launched (detached).")
logging.info(
"Hydrus client launched (detached)."
)
except Exception as exc:
logging.exception(
"Failed to launch client detached: %s", exc
"Failed to launch client detached: %s",
exc
)
else:
try:
subprocess.run(cmd, cwd=str(dest), env=env)
except subprocess.CalledProcessError as e:
logging.error("hydrus client exited non-zero: %s", e)
logging.error(
"hydrus client exited non-zero: %s",
e
)
except Exception as exc:
logging.exception("Failed to run hydrus client: %s", exc)
@@ -1360,9 +1582,9 @@ def main(argv: Optional[list[str]] = None) -> int:
# Helpful hint: show the new run_client helper and direct run example
try:
helper_to_show = (
run_client_script
if (run_client_script and run_client_script.exists())
else (script_dir / "run_client.py")
run_client_script if
(run_client_script and run_client_script.exists()) else
(script_dir / "run_client.py")
)
if venv_py:
logging.info(
@@ -1375,13 +1597,15 @@ def main(argv: Optional[list[str]] = None) -> int:
)
else:
logging.info(
"To run the Hydrus client: python %s [args]", dest / "hydrus_client.py"
"To run the Hydrus client: python %s [args]",
dest / "hydrus_client.py"
)
except Exception:
pass
else:
logging.debug(
"No hydrus_client.py found to open or run (looked in %s).", client_candidates
"No hydrus_client.py found to open or run (looked in %s).",
client_candidates
)
return 0