Add YAPF style + ignore, and format tracked Python files
This commit is contained in:
@@ -23,11 +23,12 @@ get_field = sh.get_field
|
||||
from API.folder import read_sidecar, find_sidecar, API_folder_store
|
||||
from Store import Store
|
||||
|
||||
|
||||
CMDLET = Cmdlet(
|
||||
name="add-relationship",
|
||||
summary="Associate file relationships (king/alt/related) in Hydrus based on relationship tags in sidecar.",
|
||||
usage="@1-3 | add-relationship -king @4 OR add-relationship -path <file> OR @1,@2,@3 | add-relationship",
|
||||
summary=
|
||||
"Associate file relationships (king/alt/related) in Hydrus based on relationship tags in sidecar.",
|
||||
usage=
|
||||
"@1-3 | add-relationship -king @4 OR add-relationship -path <file> OR @1,@2,@3 | add-relationship",
|
||||
arg=[
|
||||
CmdletArg(
|
||||
"path",
|
||||
@@ -39,17 +40,20 @@ CMDLET = Cmdlet(
|
||||
CmdletArg(
|
||||
"-king",
|
||||
type="string",
|
||||
description="Explicitly set the king hash/file for relationships (e.g., -king @4 or -king hash)",
|
||||
description=
|
||||
"Explicitly set the king hash/file for relationships (e.g., -king @4 or -king hash)",
|
||||
),
|
||||
CmdletArg(
|
||||
"-alt",
|
||||
type="string",
|
||||
description="Explicitly select alt item(s) by @ selection or hash list (e.g., -alt @3-5 or -alt <hash>,<hash>)",
|
||||
description=
|
||||
"Explicitly select alt item(s) by @ selection or hash list (e.g., -alt @3-5 or -alt <hash>,<hash>)",
|
||||
),
|
||||
CmdletArg(
|
||||
"-type",
|
||||
type="string",
|
||||
description="Relationship type for piped items (default: 'alt', options: 'king', 'alt', 'related')",
|
||||
description=
|
||||
"Relationship type for piped items (default: 'alt', options: 'king', 'alt', 'related')",
|
||||
),
|
||||
],
|
||||
detail=[
|
||||
@@ -84,7 +88,8 @@ def _extract_relationships_from_tag(tag_value: str) -> Dict[str, list[str]]:
|
||||
|
||||
Returns a dict like {"king": ["HASH1"], "alt": ["HASH2"], ...}
|
||||
"""
|
||||
result: Dict[str, list[str]] = {}
|
||||
result: Dict[str,
|
||||
list[str]] = {}
|
||||
if not isinstance(tag_value, str):
|
||||
return result
|
||||
|
||||
@@ -126,7 +131,8 @@ def _apply_relationships_from_tags(
|
||||
hydrus_client: Any,
|
||||
use_local_storage: bool,
|
||||
local_storage_path: Optional[Path],
|
||||
config: Dict[str, Any],
|
||||
config: Dict[str,
|
||||
Any],
|
||||
) -> int:
|
||||
"""Persist relationship tags into Hydrus or local DB.
|
||||
|
||||
@@ -135,8 +141,7 @@ def _apply_relationships_from_tags(
|
||||
- Store directional alt -> king relationships (no reverse edge).
|
||||
"""
|
||||
rel_tags = [
|
||||
t
|
||||
for t in relationship_tags
|
||||
t for t in relationship_tags
|
||||
if isinstance(t, str) and t.strip().lower().startswith("relationship:")
|
||||
]
|
||||
if not rel_tags:
|
||||
@@ -196,7 +201,12 @@ def _apply_relationships_from_tags(
|
||||
continue
|
||||
if (alt_norm, king_norm) in processed_pairs:
|
||||
continue
|
||||
db.set_relationship_by_hash(alt_norm, king_norm, "alt", bidirectional=False)
|
||||
db.set_relationship_by_hash(
|
||||
alt_norm,
|
||||
king_norm,
|
||||
"alt",
|
||||
bidirectional=False
|
||||
)
|
||||
processed_pairs.add((alt_norm, king_norm))
|
||||
except Exception:
|
||||
return 1
|
||||
@@ -270,7 +280,10 @@ def _resolve_items_from_at(token: str) -> Optional[list[Any]]:
|
||||
def _extract_hash_and_store(item: Any) -> tuple[Optional[str], Optional[str]]:
|
||||
"""Extract (hash_hex, store) from a result item (dict/object)."""
|
||||
try:
|
||||
h = get_field(item, "hash_hex") or get_field(item, "hash") or get_field(item, "file_hash")
|
||||
h = get_field(item,
|
||||
"hash_hex") or get_field(item,
|
||||
"hash") or get_field(item,
|
||||
"file_hash")
|
||||
s = get_field(item, "store")
|
||||
|
||||
hash_norm = _normalise_hash_hex(str(h) if h is not None else None)
|
||||
@@ -336,7 +349,10 @@ def _resolve_king_reference(king_arg: str) -> Optional[str]:
|
||||
|
||||
item = selected[0]
|
||||
item_hash = (
|
||||
get_field(item, "hash_hex") or get_field(item, "hash") or get_field(item, "file_hash")
|
||||
get_field(item,
|
||||
"hash_hex") or get_field(item,
|
||||
"hash") or get_field(item,
|
||||
"file_hash")
|
||||
)
|
||||
|
||||
if item_hash:
|
||||
@@ -354,7 +370,8 @@ def _refresh_relationship_view_if_current(
|
||||
target_hash: Optional[str],
|
||||
target_path: Optional[str],
|
||||
other: Optional[str],
|
||||
config: Dict[str, Any],
|
||||
config: Dict[str,
|
||||
Any],
|
||||
) -> None:
|
||||
"""If the current subject matches the target, refresh relationships via get-relationship."""
|
||||
try:
|
||||
@@ -385,29 +402,29 @@ def _refresh_relationship_view_if_current(
|
||||
subj_paths: list[str] = []
|
||||
if isinstance(subject, dict):
|
||||
subj_hashes = [
|
||||
norm(v)
|
||||
for v in [
|
||||
norm(v) for v in [
|
||||
subject.get("hydrus_hash"),
|
||||
subject.get("hash"),
|
||||
subject.get("hash_hex"),
|
||||
subject.get("file_hash"),
|
||||
]
|
||||
if v
|
||||
subject.get("file_hash"), ] if v
|
||||
]
|
||||
subj_paths = [
|
||||
norm(v)
|
||||
for v in [subject.get("file_path"), subject.get("path"), subject.get("target")]
|
||||
norm(v) for v in
|
||||
[subject.get("file_path"), subject.get("path"), subject.get("target")]
|
||||
if v
|
||||
]
|
||||
else:
|
||||
subj_hashes = [
|
||||
norm(getattr(subject, f, None))
|
||||
norm(getattr(subject,
|
||||
f,
|
||||
None))
|
||||
for f in ("hydrus_hash", "hash", "hash_hex", "file_hash")
|
||||
if getattr(subject, f, None)
|
||||
]
|
||||
subj_paths = [
|
||||
norm(getattr(subject, f, None))
|
||||
for f in ("file_path", "path", "target")
|
||||
norm(getattr(subject,
|
||||
f,
|
||||
None)) for f in ("file_path", "path", "target")
|
||||
if getattr(subject, f, None)
|
||||
]
|
||||
|
||||
@@ -472,12 +489,17 @@ def _run(result: Any, _args: Sequence[str], config: Dict[str, Any]) -> int:
|
||||
if alt_text.startswith("@"):
|
||||
selected = _resolve_items_from_at(alt_text)
|
||||
if not selected:
|
||||
log(f"Failed to resolve -alt {alt_text}: no selection context", file=sys.stderr)
|
||||
log(
|
||||
f"Failed to resolve -alt {alt_text}: no selection context",
|
||||
file=sys.stderr
|
||||
)
|
||||
return 1
|
||||
resolved_alt_items = selected
|
||||
else:
|
||||
# Treat as comma/semicolon-separated list of hashes
|
||||
parts = [p.strip() for p in alt_text.replace(";", ",").split(",") if p.strip()]
|
||||
parts = [
|
||||
p.strip() for p in alt_text.replace(";", ",").split(",") if p.strip()
|
||||
]
|
||||
hashes = [h for h in (_normalise_hash_hex(p) for p in parts) if h]
|
||||
if not hashes:
|
||||
log(
|
||||
@@ -486,25 +508,46 @@ def _run(result: Any, _args: Sequence[str], config: Dict[str, Any]) -> int:
|
||||
)
|
||||
return 1
|
||||
if not override_store:
|
||||
log("-store is required when using -alt with a raw hash list", file=sys.stderr)
|
||||
log(
|
||||
"-store is required when using -alt with a raw hash list",
|
||||
file=sys.stderr
|
||||
)
|
||||
return 1
|
||||
resolved_alt_items = [{"hash": h, "store": str(override_store)} for h in hashes]
|
||||
resolved_alt_items = [
|
||||
{
|
||||
"hash": h,
|
||||
"store": str(override_store)
|
||||
} for h in hashes
|
||||
]
|
||||
items_to_process = normalize_result_input(resolved_alt_items)
|
||||
|
||||
# Allow explicit store/hash-first operation via -query "hash:<sha256>" (supports multiple hash: tokens)
|
||||
if (not items_to_process) and override_hashes:
|
||||
if not override_store:
|
||||
log("-store is required when using -query without piped items", file=sys.stderr)
|
||||
log(
|
||||
"-store is required when using -query without piped items",
|
||||
file=sys.stderr
|
||||
)
|
||||
return 1
|
||||
items_to_process = [{"hash": h, "store": str(override_store)} for h in override_hashes]
|
||||
items_to_process = [
|
||||
{
|
||||
"hash": h,
|
||||
"store": str(override_store)
|
||||
} for h in override_hashes
|
||||
]
|
||||
|
||||
if not items_to_process and not arg_path:
|
||||
log("No items provided to add-relationship (no piped result and no -path)", file=sys.stderr)
|
||||
log(
|
||||
"No items provided to add-relationship (no piped result and no -path)",
|
||||
file=sys.stderr
|
||||
)
|
||||
return 1
|
||||
|
||||
# If no items from pipeline, just process the -path arg
|
||||
if not items_to_process and arg_path:
|
||||
items_to_process = [{"file_path": arg_path}]
|
||||
items_to_process = [{
|
||||
"file_path": arg_path
|
||||
}]
|
||||
|
||||
# Resolve the king reference once (if provided)
|
||||
king_hash: Optional[str] = None
|
||||
@@ -514,7 +557,10 @@ def _run(result: Any, _args: Sequence[str], config: Dict[str, Any]) -> int:
|
||||
if king_text.startswith("@"):
|
||||
selected = _resolve_items_from_at(king_text)
|
||||
if not selected:
|
||||
log(f"Cannot resolve {king_text}: no selection context", file=sys.stderr)
|
||||
log(
|
||||
f"Cannot resolve {king_text}: no selection context",
|
||||
file=sys.stderr
|
||||
)
|
||||
return 1
|
||||
if len(selected) != 1:
|
||||
log(
|
||||
@@ -605,7 +651,7 @@ def _run(result: Any, _args: Sequence[str], config: Dict[str, Any]) -> int:
|
||||
|
||||
# Sidecar/tag import fallback DB root (legacy): if a folder store is selected, use it;
|
||||
# otherwise fall back to configured local storage path.
|
||||
from config import get_local_storage_path
|
||||
from SYS.config import get_local_storage_path
|
||||
|
||||
local_storage_root: Optional[Path] = None
|
||||
if store_root is not None:
|
||||
@@ -629,8 +675,7 @@ def _run(result: Any, _args: Sequence[str], config: Dict[str, Any]) -> int:
|
||||
if sidecar_path is not None and sidecar_path.exists():
|
||||
_, tags, _ = read_sidecar(sidecar_path)
|
||||
relationship_tags = [
|
||||
t
|
||||
for t in (tags or [])
|
||||
t for t in (tags or [])
|
||||
if isinstance(t, str) and t.lower().startswith("relationship:")
|
||||
]
|
||||
if relationship_tags:
|
||||
@@ -657,12 +702,12 @@ def _run(result: Any, _args: Sequence[str], config: Dict[str, Any]) -> int:
|
||||
if isinstance(tags_val, list):
|
||||
rel_tags_from_pipe.extend(
|
||||
[
|
||||
t
|
||||
for t in tags_val
|
||||
t for t in tags_val
|
||||
if isinstance(t, str) and t.lower().startswith("relationship:")
|
||||
]
|
||||
)
|
||||
elif isinstance(tags_val, str) and tags_val.lower().startswith("relationship:"):
|
||||
elif isinstance(tags_val,
|
||||
str) and tags_val.lower().startswith("relationship:"):
|
||||
rel_tags_from_pipe.append(tags_val)
|
||||
|
||||
if rel_tags_from_pipe:
|
||||
@@ -686,7 +731,8 @@ def _run(result: Any, _args: Sequence[str], config: Dict[str, Any]) -> int:
|
||||
first_hash = None
|
||||
for item in items_to_process:
|
||||
h, item_store = _extract_hash_and_store(item)
|
||||
if item_store and store_name and str(item_store) != str(store_name):
|
||||
if item_store and store_name and str(item_store) != str(
|
||||
store_name):
|
||||
log(
|
||||
f"Cross-store relationship blocked: item store '{item_store}' != '{store_name}'",
|
||||
file=sys.stderr,
|
||||
@@ -700,7 +746,10 @@ def _run(result: Any, _args: Sequence[str], config: Dict[str, Any]) -> int:
|
||||
# directional alt -> king by default for local DB
|
||||
bidirectional = str(rel_type).lower() != "alt"
|
||||
db.set_relationship_by_hash(
|
||||
h, first_hash, str(rel_type), bidirectional=bidirectional
|
||||
h,
|
||||
first_hash,
|
||||
str(rel_type),
|
||||
bidirectional=bidirectional
|
||||
)
|
||||
return 0
|
||||
|
||||
@@ -717,7 +766,10 @@ def _run(result: Any, _args: Sequence[str], config: Dict[str, Any]) -> int:
|
||||
continue
|
||||
bidirectional = str(rel_type).lower() != "alt"
|
||||
db.set_relationship_by_hash(
|
||||
h, king_hash, str(rel_type), bidirectional=bidirectional
|
||||
h,
|
||||
king_hash,
|
||||
str(rel_type),
|
||||
bidirectional=bidirectional
|
||||
)
|
||||
return 0
|
||||
except Exception as exc:
|
||||
@@ -798,15 +850,21 @@ def _run(result: Any, _args: Sequence[str], config: Dict[str, Any]) -> int:
|
||||
|
||||
if isinstance(item, dict):
|
||||
file_hash = item.get("hash_hex") or item.get("hash")
|
||||
file_path_from_result = item.get("file_path") or item.get("path") or item.get("target")
|
||||
file_path_from_result = item.get("file_path") or item.get(
|
||||
"path"
|
||||
) or item.get("target")
|
||||
else:
|
||||
file_hash = getattr(item, "hash_hex", None) or getattr(item, "hash", None)
|
||||
file_path_from_result = getattr(item, "file_path", None) or getattr(item, "path", None)
|
||||
file_path_from_result = getattr(item,
|
||||
"file_path",
|
||||
None) or getattr(item,
|
||||
"path",
|
||||
None)
|
||||
|
||||
# Legacy LOCAL STORAGE MODE: Handle relationships for local files
|
||||
# (kept for -path sidecar workflows; store/hash mode above is preferred)
|
||||
from API.folder import LocalLibrarySearchOptimizer
|
||||
from config import get_local_storage_path
|
||||
from SYS.config import get_local_storage_path
|
||||
|
||||
local_storage_path = get_local_storage_path(config) if config else None
|
||||
use_local_storage = bool(local_storage_path)
|
||||
@@ -847,20 +905,27 @@ def _run(result: Any, _args: Sequence[str], config: Dict[str, Any]) -> int:
|
||||
king_file_path = opt.db.search_hash(normalized_king)
|
||||
if not king_file_path:
|
||||
log(
|
||||
f"King hash not found in local DB: {king_hash}", file=sys.stderr
|
||||
f"King hash not found in local DB: {king_hash}",
|
||||
file=sys.stderr
|
||||
)
|
||||
return 1
|
||||
|
||||
bidirectional = str(rel_type).lower() != "alt"
|
||||
opt.db.set_relationship(
|
||||
file_path_obj, king_file_path, rel_type, bidirectional=bidirectional
|
||||
file_path_obj,
|
||||
king_file_path,
|
||||
rel_type,
|
||||
bidirectional=bidirectional
|
||||
)
|
||||
log(
|
||||
f"Set {rel_type} relationship: {file_path_obj.name} -> {king_file_path.name}",
|
||||
file=sys.stderr,
|
||||
)
|
||||
_refresh_relationship_view_if_current(
|
||||
None, str(file_path_obj), str(king_file_path), config
|
||||
None,
|
||||
str(file_path_obj),
|
||||
str(king_file_path),
|
||||
config
|
||||
)
|
||||
else:
|
||||
# Original behavior: first becomes king, rest become alts
|
||||
@@ -871,7 +936,10 @@ def _run(result: Any, _args: Sequence[str], config: Dict[str, Any]) -> int:
|
||||
|
||||
if not king_path:
|
||||
try:
|
||||
ctx.store_value("relationship_king_path", str(file_path_obj))
|
||||
ctx.store_value(
|
||||
"relationship_king_path",
|
||||
str(file_path_obj)
|
||||
)
|
||||
log(
|
||||
f"Established king file: {file_path_obj.name}",
|
||||
file=sys.stderr,
|
||||
@@ -893,7 +961,10 @@ def _run(result: Any, _args: Sequence[str], config: Dict[str, Any]) -> int:
|
||||
file=sys.stderr,
|
||||
)
|
||||
_refresh_relationship_view_if_current(
|
||||
None, str(file_path_obj), str(king_path), config
|
||||
None,
|
||||
str(file_path_obj),
|
||||
str(king_path),
|
||||
config
|
||||
)
|
||||
except Exception as exc:
|
||||
log(f"Local storage error: {exc}", file=sys.stderr)
|
||||
@@ -902,7 +973,9 @@ def _run(result: Any, _args: Sequence[str], config: Dict[str, Any]) -> int:
|
||||
|
||||
# PIPELINE MODE with Hydrus: Track relationships using hash
|
||||
if file_hash and hydrus_client:
|
||||
file_hash = _normalise_hash_hex(str(file_hash) if file_hash is not None else None)
|
||||
file_hash = _normalise_hash_hex(
|
||||
str(file_hash) if file_hash is not None else None
|
||||
)
|
||||
if not file_hash:
|
||||
log("Invalid file hash format", file=sys.stderr)
|
||||
return 1
|
||||
@@ -917,7 +990,8 @@ def _run(result: Any, _args: Sequence[str], config: Dict[str, Any]) -> int:
|
||||
)
|
||||
_refresh_relationship_view_if_current(
|
||||
file_hash,
|
||||
str(file_path_from_result) if file_path_from_result is not None else None,
|
||||
str(file_path_from_result)
|
||||
if file_path_from_result is not None else None,
|
||||
king_hash,
|
||||
config,
|
||||
)
|
||||
@@ -943,7 +1017,11 @@ def _run(result: Any, _args: Sequence[str], config: Dict[str, Any]) -> int:
|
||||
# If we already have a king and this is a different hash, link them
|
||||
if existing_king and existing_king != file_hash:
|
||||
try:
|
||||
hydrus_client.set_relationship(file_hash, existing_king, rel_type)
|
||||
hydrus_client.set_relationship(
|
||||
file_hash,
|
||||
existing_king,
|
||||
rel_type
|
||||
)
|
||||
log(
|
||||
f"[add-relationship] Set {rel_type} relationship: {file_hash} <-> {existing_king}",
|
||||
file=sys.stderr,
|
||||
@@ -952,8 +1030,7 @@ def _run(result: Any, _args: Sequence[str], config: Dict[str, Any]) -> int:
|
||||
file_hash,
|
||||
(
|
||||
str(file_path_from_result)
|
||||
if file_path_from_result is not None
|
||||
else None
|
||||
if file_path_from_result is not None else None
|
||||
),
|
||||
existing_king,
|
||||
config,
|
||||
@@ -975,7 +1052,9 @@ def _run(result: Any, _args: Sequence[str], config: Dict[str, Any]) -> int:
|
||||
# Resolve media path from -path arg or result target
|
||||
target = getattr(result, "target", None) or getattr(result, "path", None)
|
||||
media_path = (
|
||||
arg_path if arg_path is not None else Path(str(target)) if isinstance(target, str) else None
|
||||
arg_path
|
||||
if arg_path is not None else Path(str(target)) if isinstance(target,
|
||||
str) else None
|
||||
)
|
||||
if media_path is None:
|
||||
log("Provide -path <file> or pipe a local file result", file=sys.stderr)
|
||||
@@ -1055,7 +1134,11 @@ def _run(result: Any, _args: Sequence[str], config: Dict[str, Any]) -> int:
|
||||
continue
|
||||
|
||||
try:
|
||||
hydrus_client.set_relationship(file_hash, related_hash, rel_type)
|
||||
hydrus_client.set_relationship(
|
||||
file_hash,
|
||||
related_hash,
|
||||
rel_type
|
||||
)
|
||||
log(
|
||||
f"[add-relationship] Set {rel_type} relationship: "
|
||||
f"{file_hash} <-> {related_hash}",
|
||||
@@ -1063,7 +1146,10 @@ def _run(result: Any, _args: Sequence[str], config: Dict[str, Any]) -> int:
|
||||
)
|
||||
success_count += 1
|
||||
except Exception as exc:
|
||||
log(f"Failed to set {rel_type} relationship: {exc}", file=sys.stderr)
|
||||
log(
|
||||
f"Failed to set {rel_type} relationship: {exc}",
|
||||
file=sys.stderr
|
||||
)
|
||||
error_count += 1
|
||||
|
||||
except Exception as exc:
|
||||
@@ -1075,7 +1161,9 @@ def _run(result: Any, _args: Sequence[str], config: Dict[str, Any]) -> int:
|
||||
f"Successfully set {success_count} relationship(s) for {media_path.name}",
|
||||
file=sys.stderr,
|
||||
)
|
||||
ctx.emit(f"add-relationship: {media_path.name} ({success_count} relationships set)")
|
||||
ctx.emit(
|
||||
f"add-relationship: {media_path.name} ({success_count} relationships set)"
|
||||
)
|
||||
return 0
|
||||
elif error_count == 0:
|
||||
log(f"No relationships to set", file=sys.stderr)
|
||||
|
||||
Reference in New Issue
Block a user