Merge remote-tracking branch 'origin/main' into feat/331-collections-pr1

# Conflicts:
#	database/duplicate_copies.py
#	database/models.py
#	py/api/duplicate_files.py
#	tests/test_duplicate_image_categories.py
This commit is contained in:
Vito Sansevero
2026-10-28 08:52:10 -07:00
40 changed files with 2596 additions and 2686 deletions
+6
View File
@@ -34,6 +34,11 @@ try:
except ImportError:
from utils.parallel import map_parallel
try:
from ...utils import exit_signal
except ImportError:
from utils import exit_signal
try:
from ...database.pair_ingestion import blank_positive, ingest_resolved_pair
from ...database.run_attribution import (
@@ -118,6 +123,7 @@ def _collect_media_files(output_dirs, extensions):
if not os.path.isdir(root):
continue
for current, dirnames, filenames in os.walk(root, followlinks=False):
exit_signal.checkpoint() # #376: stop between directories at exit
depth = 0 if current == root else len(Path(current).relative_to(root).parts)
dirnames[:] = sorted(
d
+5
View File
@@ -31,6 +31,7 @@ from .inspection_service import CurrentFileChanged, inspect_current_file
try:
from ...database.execution_history import HistoryEpochChanged
from ...database.history_gallery import read_history_gallery
from ...database.run_attribution import recorded_output_lookup
from ...database.history_reads import (
HistoryCaptureChanged,
OutputPosition,
@@ -42,6 +43,7 @@ try:
except ImportError:
from database.execution_history import HistoryEpochChanged
from database.history_gallery import read_history_gallery
from database.run_attribution import recorded_output_lookup
from database.history_reads import (
HistoryCaptureChanged,
OutputPosition,
@@ -728,6 +730,9 @@ class HistoryRoutesMixin:
"library",
{"status": "unavailable" if saved_unavailable else "none"},
),
recorded=recorded_output_lookup(
db, close=True, reference=receipt["reference"]
),
)
_reauthorize_current_target(resolved, scope)
identity = {
+3
View File
@@ -62,6 +62,7 @@ try:
from ...database.image_favorites import MAX_KEY_LENGTH
from ...database.library import display_title
from ...database.models import normalize_image_path
from ...database.run_attribution import recorded_output_lookup
from ...utils.card_seed import card_seed, header_params, parsed_positive
from ...utils.parallel import map_parallel
from ...utils.json_strict import dumps_strict
@@ -83,6 +84,7 @@ except ImportError:
from database.image_favorites import MAX_KEY_LENGTH
from database.library import display_title
from database.models import normalize_image_path
from database.run_attribution import recorded_output_lookup
from utils.card_seed import card_seed, header_params, parsed_positive
from utils.parallel import map_parallel
from utils.json_strict import dumps_strict
@@ -3803,6 +3805,7 @@ class ImageRoutesMixin:
image_tags=self._inspection_image_tags(
roots[index], Path(relative).parts
),
recorded=recorded_output_lookup(self.db),
)
except CurrentFileChanged:
return None
+14 -3
View File
@@ -3,7 +3,7 @@
import os
import stat
from pathlib import Path
from typing import Callable
from typing import Any, Callable
try:
from ...utils.prompt_inspector import (
@@ -100,18 +100,23 @@ def inspect_current_file(
saved: dict | None,
library: dict,
image_tags: Callable[[int, int], dict] | None = None,
recorded: Callable[[Path], Any] | None = None,
) -> dict:
"""Current-file evidence read between two equal stats of *path*.
*image_tags*, when given, is called once with that stable
``(st_size, st_mtime_ns)`` and its facet is returned as ``image_tags``
(spec amendment S1: tags of a byte record verified at exactly that stat).
*recorded*, when given, is asked within the same stat window for what
recorded run history knows about *path* (``RecordedOutput`` or None: the
save node that wrote it and the texts PromptManager recorded, #386).
"""
before = _current_file_facts(path)
try:
inspection = read_inspection_file(path)
except MetadataReadError:
inspection = None
run = recorded(path) if recorded is not None and inspection is not None else None
after = _current_file_facts(path)
fields = ("st_dev", "st_ino", "st_mode", "st_size", "st_mtime_ns", "st_ctime_ns")
if any(getattr(before, key) != getattr(after, key) for key in fields):
@@ -129,9 +134,15 @@ def inspect_current_file(
profile = (
PREVIEW_INSPECTION_PROFILE if _in_host_temp(path) else INSPECTION_PROFILE
)
# The file name anchors on the saver that wrote it (#381).
# The file name anchors on the saver that wrote it (#381); recorded run
# history names that saver when the file alone cannot (#386).
evidence = inspect_prompt_evidence(
inspection.metadata, role_saved, profile=profile, file_name=path.name
inspection.metadata,
role_saved,
profile=profile,
file_name=path.name,
recorded_saver_id=run.saver_id if run is not None else None,
recorded_texts=run.texts if run is not None else None,
)
return {
"file": {
+16 -1
View File
@@ -47,6 +47,11 @@ import time
import uuid
from pathlib import PurePosixPath, PureWindowsPath
try:
from ...utils import exit_signal
except ImportError: # tests import the package top-level
from utils import exit_signal
logger = logging.getLogger(__name__)
# Start and finish order: wall-clock times can tie (coarse Windows clocks).
_sequence = itertools.count()
@@ -195,11 +200,21 @@ class Job:
self.counters = dict.fromkeys(groups, 0)
self.outcomes = {group: [] for group in groups}
self.log = []
self.cancelled = False
self._cancel_requested = False
self.last_event = None
self.task = None
self._subscribers = set()
@property
def cancelled(self):
"""Cancel was requested, or the interpreter is exiting (#376): every
check a job already makes between files also stops it at exit."""
return self._cancel_requested or exit_signal.requested()
@cancelled.setter
def cancelled(self, value):
self._cancel_requested = bool(value)
@property
def record_changing(self):
return self.admission == "records"
+7
View File
@@ -92,6 +92,11 @@ try:
except ImportError: # tests import the package top-level
from utils.parallel import map_parallel, note_refusal, refused_at_shutdown
try:
from ...utils.exit_signal import ExitRequested
except ImportError: # tests import the package top-level
from utils.exit_signal import ExitRequested
# The placeholder frame a video without a preview is answered with (JPEG
# bytes, never written to disk); written previews follow the settings.
PLACEHOLDER_SIZE = (thumbnail_settings.DEFAULT_EDGE, thumbnail_settings.DEFAULT_EDGE)
@@ -460,6 +465,8 @@ class AutoThumbnailer:
def _run(self):
try:
self._drain()
except ExitRequested:
pass # #376: the interpreter is exiting; queued files are dropped
except Exception:
self._logger.warning("The automatic preview worker stopped", exc_info=True)
finally: