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

@@ -89,7 +89,10 @@ class CmdletArg:
storage_flags = SharedArgs.STORAGE.to_flags()
# Returns: ('--storage', '-storage', '-s')
"""
flags = [f"--{self.name}", f"-{self.name}"] # Both double-dash and single-dash variants
flags = [
f"--{self.name}",
f"-{self.name}"
] # Both double-dash and single-dash variants
# Add short form if alias exists
if self.alias:
@@ -130,8 +133,11 @@ def QueryArg(
description=str(description or ""),
choices=list(choices or []),
handler=handler,
query_key=str(key or name).strip().lower() if str(key or name).strip() else None,
query_aliases=[str(a).strip().lower() for a in (aliases or []) if str(a).strip()],
query_key=str(key or name).strip().lower()
if str(key or name).strip() else None,
query_aliases=[
str(a).strip().lower() for a in (aliases or []) if str(a).strip()
],
query_only=bool(query_only),
)
@@ -208,9 +214,7 @@ class SharedArgs:
# If no config provided, try to load it
if config is None:
try:
from config import load_config
config = load_config()
from SYS.config import load_config
except Exception:
return []
@@ -223,7 +227,9 @@ class SharedArgs:
LOCATION = CmdletArg(
"location",
type="enum",
choices=["hydrus", "0x0", "local"],
choices=["hydrus",
"0x0",
"local"],
required=True,
description="Destination location",
)
@@ -257,15 +263,25 @@ class SharedArgs:
LIBRARY = CmdletArg(
"library",
type="string",
choices=["hydrus", "local", "soulseek", "libgen", "ftp"],
choices=["hydrus",
"local",
"soulseek",
"libgen",
"ftp"],
description="Search library or source location.",
)
TIMEOUT = CmdletArg(
"timeout", type="integer", description="Search or operation timeout in seconds."
"timeout",
type="integer",
description="Search or operation timeout in seconds."
)
LIMIT = CmdletArg("limit", type="integer", description="Maximum number of results to return.")
LIMIT = CmdletArg(
"limit",
type="integer",
description="Maximum number of results to return."
)
# Path/File arguments
PATH = CmdletArg("path", type="string", description="File or directory path.")
@@ -280,18 +296,24 @@ class SharedArgs:
)
REASON = CmdletArg(
"reason", type="string", description="Reason or explanation for the operation."
"reason",
type="string",
description="Reason or explanation for the operation."
)
ARCHIVE = CmdletArg(
"archive",
type="flag",
description="Archive the URL to Wayback Machine, Archive.today, and Archive.ph (requires URL argument in cmdlet).",
description=
"Archive the URL to Wayback Machine, Archive.today, and Archive.ph (requires URL argument in cmdlet).",
alias="arch",
)
@staticmethod
def resolve_storage(storage_value: Optional[str], default: Optional[Path] = None) -> Path:
def resolve_storage(
storage_value: Optional[str],
default: Optional[Path] = None
) -> Path:
"""Resolve a storage location name to a filesystem Path.
Maps storage identifiers (hydrus, local, ftp) to their actual
@@ -394,7 +416,11 @@ class Cmdlet:
detail: List[str] = field(default_factory=list)
"""Detailed explanation lines (for help text)"""
# Execution function: func(result, args, config) -> int
exec: Optional[Callable[[Any, Sequence[str], Dict[str, Any]], int]] = field(default=None)
exec: Optional[Callable[[Any,
Sequence[str],
Dict[str,
Any]],
int]] = field(default=None)
def _collect_names(self) -> List[str]:
"""Collect primary name plus aliases, de-duplicated and normalized."""
@@ -450,7 +476,8 @@ class Cmdlet:
if low in cmdlet.get_flags('library'):
# handle library flag
"""
return {f"-{arg_name}", f"--{arg_name}"}
return {f"-{arg_name}",
f"--{arg_name}"}
def build_flag_registry(self) -> Dict[str, set[str]]:
"""Build a registry of all flag variants for this cmdlet's arguments.
@@ -470,7 +497,10 @@ class Cmdlet:
elif low in flags.get('tag', set()):
# handle tag
"""
return {arg.name: self.get_flags(arg.name) for arg in self.arg}
return {
arg.name: self.get_flags(arg.name)
for arg in self.arg
}
# Tag groups cache (loaded from JSON config file)
@@ -487,7 +517,10 @@ def set_tag_groups_path(path: Path) -> None:
TAG_GROUPS_PATH = path
def parse_cmdlet_args(args: Sequence[str], cmdlet_spec: Dict[str, Any] | Cmdlet) -> Dict[str, Any]:
def parse_cmdlet_args(args: Sequence[str],
cmdlet_spec: Dict[str,
Any] | Cmdlet) -> Dict[str,
Any]:
"""Parse command-line arguments based on cmdlet specification.
Extracts argument values from command-line tokens using the argument names
@@ -515,7 +548,8 @@ def parse_cmdlet_args(args: Sequence[str], cmdlet_spec: Dict[str, Any] | Cmdlet)
result = parse_cmdlet_args(["value1", "-count", "5"], cmdlet)
# result = {"path": "value1", "count": "5"}
"""
result: Dict[str, Any] = {}
result: Dict[str,
Any] = {}
# Only accept Cmdlet objects
if not isinstance(cmdlet_spec, Cmdlet):
@@ -527,7 +561,8 @@ def parse_cmdlet_args(args: Sequence[str], cmdlet_spec: Dict[str, Any] | Cmdlet)
flagged_args: List[CmdletArg] = [] # args with prefix in definition
query_mapped_args: List[CmdletArg] = []
arg_spec_map: Dict[str, str] = {} # prefix variant -> canonical name (without prefix)
arg_spec_map: Dict[str,
str] = {} # prefix variant -> canonical name (without prefix)
for spec in arg_specs:
name = spec.name
@@ -572,7 +607,8 @@ def parse_cmdlet_args(args: Sequence[str], cmdlet_spec: Dict[str, Any] | Cmdlet)
# Legacy guidance: -hash/--hash was removed in favor of -query "hash:...".
# However, some cmdlets may explicitly re-introduce a -hash flag.
if token_lower in {"-hash", "--hash"} and token_lower not in arg_spec_map:
if token_lower in {"-hash",
"--hash"} and token_lower not in arg_spec_map:
try:
log(
'Legacy flag -hash is no longer supported. Use: -query "hash:<sha256>"',
@@ -587,7 +623,10 @@ def parse_cmdlet_args(args: Sequence[str], cmdlet_spec: Dict[str, Any] | Cmdlet)
if token_lower in arg_spec_map:
canonical_name = arg_spec_map[token_lower]
spec = next(
(s for s in arg_specs if str(s.name).lstrip("-").lower() == canonical_name.lower()),
(
s for s in arg_specs
if str(s.name).lstrip("-").lower() == canonical_name.lower()
),
None,
)
@@ -650,14 +689,18 @@ def parse_cmdlet_args(args: Sequence[str], cmdlet_spec: Dict[str, Any] | Cmdlet)
if query_mapped_args and raw_query is not None:
try:
from cli_syntax import parse_query as _parse_query
from SYS.cli_syntax import parse_query as _parse_query
parsed_query = _parse_query(str(raw_query))
fields = parsed_query.get("fields", {}) if isinstance(parsed_query, dict) else {}
fields = parsed_query.get("fields",
{}) if isinstance(parsed_query,
dict) else {}
norm_fields = (
{str(k).strip().lower(): v for k, v in fields.items()}
if isinstance(fields, dict)
else {}
{
str(k).strip().lower(): v
for k, v in fields.items()
} if isinstance(fields,
dict) else {}
)
except Exception:
norm_fields = {}
@@ -667,12 +710,15 @@ def parse_cmdlet_args(args: Sequence[str], cmdlet_spec: Dict[str, Any] | Cmdlet)
if not canonical_name:
continue
# Do not override explicit flags.
if canonical_name in result and result.get(canonical_name) not in (None, ""):
if canonical_name in result and result.get(canonical_name) not in (None,
""):
continue
try:
key = str(getattr(spec, "query_key", "") or "").strip().lower()
aliases = getattr(spec, "query_aliases", None)
alias_list = [str(a).strip().lower() for a in (aliases or []) if str(a).strip()]
alias_list = [
str(a).strip().lower() for a in (aliases or []) if str(a).strip()
]
except Exception:
key = ""
alias_list = []
@@ -761,7 +807,9 @@ def parse_single_hash_query(query: Optional[str]) -> Optional[str]:
def get_hash_for_operation(
override_hash: Optional[str], result: Any, field_name: str = "hash"
override_hash: Optional[str],
result: Any,
field_name: str = "hash"
) -> Optional[str]:
"""Get normalized hash from override or result object, consolidating common pattern.
@@ -778,9 +826,12 @@ def get_hash_for_operation(
if override_hash:
return normalize_hash(override_hash)
hash_value = (
get_field(result, field_name)
or getattr(result, field_name, None)
or getattr(result, "hash", None)
get_field(result,
field_name) or getattr(result,
field_name,
None) or getattr(result,
"hash",
None)
)
return normalize_hash(hash_value)
@@ -792,7 +843,9 @@ def fetch_hydrus_metadata(
store_name: Optional[str] = None,
hydrus_client: Any = None,
**kwargs,
) -> tuple[Optional[Dict[str, Any]], Optional[int]]:
) -> tuple[Optional[Dict[str,
Any]],
Optional[int]]:
"""Fetch metadata from Hydrus for a given hash, consolidating common fetch pattern.
Eliminates repeated boilerplate: client initialization, error handling, metadata extraction.
@@ -850,7 +903,11 @@ def fetch_hydrus_metadata(
return None, 1
items = payload.get("metadata") if isinstance(payload, dict) else None
meta = items[0] if (isinstance(items, list) and items and isinstance(items[0], dict)) else None
meta = items[0] if (
isinstance(items,
list) and items and isinstance(items[0],
dict)
) else None
return meta, 0
@@ -914,7 +971,14 @@ def should_show_help(args: Sequence[str]) -> bool:
return 0
"""
try:
return any(str(a).lower() in {"-?", "/?", "--help", "-h", "help", "--cmdlet"} for a in args)
return any(
str(a).lower() in {"-?",
"/?",
"--help",
"-h",
"help",
"--cmdlet"} for a in args
)
except Exception:
return False
@@ -963,8 +1027,12 @@ def pipeline_item_local_path(item: Any) -> Optional[str]:
def collect_relationship_labels(
payload: Any, label_stack: List[str] | None = None, mapping: Dict[str, str] | None = None
) -> Dict[str, str]:
payload: Any,
label_stack: List[str] | None = None,
mapping: Dict[str,
str] | None = None
) -> Dict[str,
str]:
"""Recursively extract hash-to-label mappings from nested relationship data.
Walks through nested dicts/lists looking for sha256-like strings (64 hex chars)
@@ -1149,7 +1217,8 @@ def _load_tag_groups() -> Dict[str, List[str]]:
_TAG_GROUPS_MTIME = mtime
return {}
groups: Dict[str, List[str]] = {}
groups: Dict[str,
List[str]] = {}
if isinstance(payload, dict):
for key, value in payload.items():
if not isinstance(key, str):
@@ -1167,7 +1236,8 @@ def _load_tag_groups() -> Dict[str, List[str]]:
normalised = _normalise_tag_group_entry(value)
if normalised:
members.extend(
token.strip() for token in normalised.split(",") if token.strip()
token.strip() for token in normalised.split(",")
if token.strip()
)
if members:
groups[name] = members
@@ -1201,19 +1271,24 @@ def expand_tag_groups(raw_tags: Iterable[str]) -> List[str]:
candidate = token.strip()
if not candidate:
continue
if candidate.startswith("{") and candidate.endswith("}") and len(candidate) > 2:
if candidate.startswith("{") and candidate.endswith("}") and len(candidate
) > 2:
name = candidate[1:-1].strip().lower()
if not name:
continue
if name in seen:
log(f"Tag group recursion detected for {{{name}}}; skipping", file=sys.stderr)
log(
f"Tag group recursion detected for {{{name}}}; skipping",
file=sys.stderr
)
continue
members = groups.get(name)
if not members:
log(f"Unknown tag group {{{name}}}", file=sys.stderr)
result.append(candidate)
continue
result.extend(_expand(members, seen | {name}))
result.extend(_expand(members,
seen | {name}))
else:
result.append(candidate)
return result
@@ -1291,7 +1366,8 @@ def create_pipe_object_result(
parent_hash: Optional[str] = None,
tag: Optional[List[str]] = None,
**extra: Any,
) -> Dict[str, Any]:
) -> Dict[str,
Any]:
"""Create a PipeObject-compatible result dict for pipeline chaining.
This is a helper to emit results in the standard format that downstream
@@ -1395,7 +1471,8 @@ def _extract_flag_value(args: Sequence[str], *flags: str) -> Optional[str]:
"""
if not args:
return None
want = {str(f).strip().lower() for f in flags if str(f).strip()}
want = {str(f).strip().lower()
for f in flags if str(f).strip()}
if not want:
return None
try:
@@ -1499,7 +1576,9 @@ def apply_output_path_from_pipeobjects(
try:
dest_str = str(dest_raw).strip()
if "://" in dest_str:
_print_live_safe_stderr(f"Ignoring -path value that looks like a URL: {dest_str}")
_print_live_safe_stderr(
f"Ignoring -path value that looks like a URL: {dest_str}"
)
return list(emits or [])
except Exception:
pass
@@ -1564,7 +1643,9 @@ def apply_output_path_from_pipeobjects(
try:
dest_dir.mkdir(parents=True, exist_ok=True)
except Exception as exc:
_print_live_safe_stderr(f"Failed to create destination directory: {dest_dir} ({exc})")
_print_live_safe_stderr(
f"Failed to create destination directory: {dest_dir} ({exc})"
)
return items
for idx, src in zip(artifact_indices, artifact_paths):
@@ -1572,7 +1653,11 @@ def apply_output_path_from_pipeobjects(
final = _unique_destination_path(final)
try:
if src.resolve() == final.resolve():
_apply_saved_path_update(items[idx], old_path=str(src), new_path=str(final))
_apply_saved_path_update(
items[idx],
old_path=str(src),
new_path=str(final)
)
_print_saved_output_panel(items[idx], final)
continue
except Exception:
@@ -1602,7 +1687,9 @@ def apply_output_path_from_pipeobjects(
try:
final.parent.mkdir(parents=True, exist_ok=True)
except Exception as exc:
_print_live_safe_stderr(f"Failed to create destination directory: {final.parent} ({exc})")
_print_live_safe_stderr(
f"Failed to create destination directory: {final.parent} ({exc})"
)
return items
final = _unique_destination_path(final)
@@ -1667,7 +1754,9 @@ def _print_saved_output_panel(item: Any, final_path: Path) -> None:
file_hash = ""
try:
file_hash = str(get_field(item, "hash") or get_field(item, "sha256") or "").strip()
file_hash = str(get_field(item,
"hash") or get_field(item,
"sha256") or "").strip()
except Exception:
file_hash = ""
if not file_hash:
@@ -1742,13 +1831,15 @@ def get_pipe_object_hash(pipe_object: Any) -> Optional[str]:
"""Extract file hash from PipeObject, dict, or pipeline-friendly object."""
if pipe_object is None:
return None
for attr in ("hash",):
for attr in ("hash",
):
if hasattr(pipe_object, attr):
value = getattr(pipe_object, attr)
if value:
return value
if isinstance(pipe_object, dict):
for key in ("hash",):
for key in ("hash",
):
value = pipe_object.get(key)
if value:
return value
@@ -1832,7 +1923,8 @@ def filter_results_by_temp(results: List[Any], include_temp: bool = False) -> Li
return filtered
def merge_sequences(*sources: Optional[Iterable[Any]], case_sensitive: bool = True) -> list[str]:
def merge_sequences(*sources: Optional[Iterable[Any]],
case_sensitive: bool = True) -> list[str]:
"""Merge iterable sources while preserving order and removing duplicates."""
seen: set[str] = set()
merged: list[str] = []
@@ -1858,7 +1950,9 @@ def merge_sequences(*sources: Optional[Iterable[Any]], case_sensitive: bool = Tr
def collapse_namespace_tags(
tags: Optional[Iterable[Any]], namespace: str, prefer: str = "last"
tags: Optional[Iterable[Any]],
namespace: str,
prefer: str = "last"
) -> list[str]:
"""Reduce tags so only one entry for a given namespace remains.
@@ -1901,7 +1995,9 @@ def collapse_namespace_tags(
def collapse_namespace_tag(
tags: Optional[Iterable[Any]], namespace: str, prefer: str = "last"
tags: Optional[Iterable[Any]],
namespace: str,
prefer: str = "last"
) -> list[str]:
"""Singular alias for collapse_namespace_tags.
@@ -2020,7 +2116,10 @@ def extract_duration(result: Any) -> Optional[float]:
return None
def coerce_to_pipe_object(value: Any, default_path: Optional[str] = None) -> models.PipeObject:
def coerce_to_pipe_object(
value: Any,
default_path: Optional[str] = None
) -> models.PipeObject:
"""Normalize any incoming result to a PipeObject for single-source-of-truth state.
Uses hash+store canonical pattern.
@@ -2029,11 +2128,9 @@ def coerce_to_pipe_object(value: Any, default_path: Optional[str] = None) -> mod
try:
from SYS.logger import is_debug_enabled, debug
if (
is_debug_enabled()
and hasattr(value, "__class__")
and value.__class__.__name__ == "ResultItem"
):
if (is_debug_enabled() and hasattr(value,
"__class__")
and value.__class__.__name__ == "ResultItem"):
debug("[ResultItem -> PipeObject conversion]")
debug(f" title={getattr(value, 'title', None)}")
debug(f" target={getattr(value, 'target', None)}")
@@ -2081,29 +2178,30 @@ def coerce_to_pipe_object(value: Any, default_path: Optional[str] = None) -> mod
value = value.to_dict()
elif not isinstance(value, dict):
try:
obj_map: Dict[str, Any] = {}
obj_map: Dict[str,
Any] = {}
for k in (
"hash",
"store",
"provider",
"prov",
"tag",
"title",
"url",
"source_url",
"duration",
"duration_seconds",
"metadata",
"full_metadata",
"warnings",
"path",
"target",
"relationships",
"is_temp",
"action",
"parent_hash",
"extra",
"media_kind",
"hash",
"store",
"provider",
"prov",
"tag",
"title",
"url",
"source_url",
"duration",
"duration_seconds",
"metadata",
"full_metadata",
"warnings",
"path",
"target",
"relationships",
"is_temp",
"action",
"parent_hash",
"extra",
"media_kind",
):
if hasattr(value, k):
obj_map[k] = getattr(value, k)
@@ -2118,7 +2216,8 @@ def coerce_to_pipe_object(value: Any, default_path: Optional[str] = None) -> mod
store_val = value.get("store") or "PATH"
if not store_val or store_val == "PATH":
try:
extra_store = value.get("extra", {}).get("store")
extra_store = value.get("extra",
{}).get("store")
except Exception:
extra_store = None
if extra_store:
@@ -2150,7 +2249,10 @@ def coerce_to_pipe_object(value: Any, default_path: Optional[str] = None) -> mod
except Exception:
pass
extra = {k: v for k, v in value.items() if k not in known_keys}
extra = {
k: v
for k, v in value.items() if k not in known_keys
}
# Extract URL: prefer direct url field, then url list
from metadata import normalize_urls
@@ -2177,17 +2279,16 @@ def coerce_to_pipe_object(value: Any, default_path: Optional[str] = None) -> mod
# Only use target as path if it's not a URL (url should stay in url field)
if not path_val and "target" in value:
target = value["target"]
if target and not (
isinstance(target, str)
and (target.startswith("http://") or target.startswith("https://"))
):
if target and not (isinstance(target,
str) and (target.startswith("http://")
or target.startswith("https://"))):
path_val = target
# If the path value is actually a URL, move it to url_val and clear path_val
try:
if isinstance(path_val, str) and (
path_val.startswith("http://") or path_val.startswith("https://")
):
if isinstance(path_val,
str) and (path_val.startswith("http://")
or path_val.startswith("https://")):
# Prefer existing url_val if present, otherwise move path_val into url_val
if not url_val:
url_val = path_val
@@ -2203,9 +2304,9 @@ def coerce_to_pipe_object(value: Any, default_path: Optional[str] = None) -> mod
hash=hash_val,
store=store_val,
provider=str(
value.get("provider") or value.get("prov") or extra.get("provider") or ""
).strip()
or None,
value.get("provider") or value.get("prov") or extra.get("provider")
or ""
).strip() or None,
tag=tag_val,
title=title_val,
url=url_val,
@@ -2215,7 +2316,8 @@ def coerce_to_pipe_object(value: Any, default_path: Optional[str] = None) -> mod
warnings=list(value.get("warnings") or []),
path=path_val,
relationships=rels,
is_temp=bool(value.get("is_temp", False)),
is_temp=bool(value.get("is_temp",
False)),
action=value.get("action"),
parent_hash=value.get("parent_hash"),
extra=extra,
@@ -2270,7 +2372,11 @@ def coerce_to_pipe_object(value: Any, default_path: Optional[str] = None) -> mod
return pipe_obj
def register_url_with_local_library(pipe_obj: models.PipeObject, config: Dict[str, Any]) -> bool:
def register_url_with_local_library(
pipe_obj: models.PipeObject,
config: Dict[str,
Any]
) -> bool:
"""Register url with a file in the local library database.
This is called automatically by download cmdlet to ensure url are persisted
@@ -2285,7 +2391,7 @@ def register_url_with_local_library(pipe_obj: models.PipeObject, config: Dict[st
"""
try:
from config import get_local_storage_path
from SYS.config import get_local_storage_path
from API.folder import API_folder_store
file_path = get_field(pipe_obj, "path")