diff --git a/.github/workflows/python-tests.yml b/.github/workflows/python-tests.yml index 21266044..ace177e4 100644 --- a/.github/workflows/python-tests.yml +++ b/.github/workflows/python-tests.yml @@ -13,8 +13,28 @@ jobs: runs-on: ubuntu-22.04 permissions: contents: read + env: + GIT_HTTP_LOW_SPEED_LIMIT: "1000" + GIT_HTTP_LOW_SPEED_TIME: "60" steps: - - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + - name: Tune Git HTTP settings + run: | + git config --global http.lowSpeedLimit "$GIT_HTTP_LOW_SPEED_LIMIT" + git config --global http.lowSpeedTime "$GIT_HTTP_LOW_SPEED_TIME" + git config --global http.version HTTP/1.1 + + - name: Checkout repository + id: checkout_primary + continue-on-error: true + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + fetch-depth: 0 + + - name: Retry checkout after transient failure + if: steps.checkout_primary.outcome == 'failure' + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + fetch-depth: 0 - name: Set up Python uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0 @@ -52,8 +72,28 @@ jobs: runs-on: ubuntu-22.04 permissions: contents: read + env: + GIT_HTTP_LOW_SPEED_LIMIT: "1000" + GIT_HTTP_LOW_SPEED_TIME: "60" steps: - - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + - name: Tune Git HTTP settings + run: | + git config --global http.lowSpeedLimit "$GIT_HTTP_LOW_SPEED_LIMIT" + git config --global http.lowSpeedTime "$GIT_HTTP_LOW_SPEED_TIME" + git config --global http.version HTTP/1.1 + + - name: Checkout repository + id: checkout_primary + continue-on-error: true + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + fetch-depth: 0 + + - name: Retry checkout after transient failure + if: steps.checkout_primary.outcome == 'failure' + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + fetch-depth: 0 - name: Set up Node.js uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0 @@ -71,12 +111,32 @@ jobs: runs-on: ubuntu-22.04 permissions: contents: read + env: + GIT_HTTP_LOW_SPEED_LIMIT: "1000" + GIT_HTTP_LOW_SPEED_TIME: "60" strategy: fail-fast: false matrix: python-version: ["3.10", "3.11", "3.12", "3.13", "3.14"] steps: - - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + - name: Tune Git HTTP settings + run: | + git config --global http.lowSpeedLimit "$GIT_HTTP_LOW_SPEED_LIMIT" + git config --global http.lowSpeedTime "$GIT_HTTP_LOW_SPEED_TIME" + git config --global http.version HTTP/1.1 + + - name: Checkout repository + id: checkout_primary + continue-on-error: true + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + fetch-depth: 0 + + - name: Retry checkout after transient failure + if: steps.checkout_primary.outcome == 'failure' + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + fetch-depth: 0 - name: Set up Python ${{ matrix.python-version }} uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0 @@ -116,8 +176,28 @@ jobs: runs-on: ubuntu-22.04 permissions: contents: read + env: + GIT_HTTP_LOW_SPEED_LIMIT: "1000" + GIT_HTTP_LOW_SPEED_TIME: "60" steps: - - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + - name: Tune Git HTTP settings + run: | + git config --global http.lowSpeedLimit "$GIT_HTTP_LOW_SPEED_LIMIT" + git config --global http.lowSpeedTime "$GIT_HTTP_LOW_SPEED_TIME" + git config --global http.version HTTP/1.1 + + - name: Checkout repository + id: checkout_primary + continue-on-error: true + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + fetch-depth: 0 + + - name: Retry checkout after transient failure + if: steps.checkout_primary.outcome == 'failure' + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + fetch-depth: 0 - name: Set up Python uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0 diff --git a/mjr_am_backend/adapters/db/sqlite_facade.py b/mjr_am_backend/adapters/db/sqlite_facade.py index f5f6d76a..ac11f62a 100644 --- a/mjr_am_backend/adapters/db/sqlite_facade.py +++ b/mjr_am_backend/adapters/db/sqlite_facade.py @@ -1759,7 +1759,10 @@ class Sqlite: ("bRestartable", wintypes.BOOL), ] - rm = ctypes.WinDLL("Rstrtmgr") + win_dll = getattr(ctypes, "WinDLL", None) + if win_dll is None: + return [] + rm = win_dll("Rstrtmgr") rm.RmStartSession.argtypes = [ctypes.POINTER(wintypes.DWORD), wintypes.DWORD, wintypes.LPWSTR] rm.RmStartSession.restype = wintypes.DWORD rm.RmRegisterResources.argtypes = [ diff --git a/mjr_am_backend/features/index/scanner.py b/mjr_am_backend/features/index/scanner.py index 7af87335..d2d3ae8d 100644 --- a/mjr_am_backend/features/index/scanner.py +++ b/mjr_am_backend/features/index/scanner.py @@ -200,7 +200,7 @@ class IndexScanner: self._vector_service, asset_id=aid, filepath=filepath, - kind=str(entry.get("kind") or "image"), + kind="image", metadata_raw=entry.get("metadata_raw"), ), timeout=_VECTOR_INDEX_PER_ASSET_TIMEOUT_S, diff --git a/mjr_am_backend/features/index/vector_service.py b/mjr_am_backend/features/index/vector_service.py index 32437491..3c83730b 100644 --- a/mjr_am_backend/features/index/vector_service.py +++ b/mjr_am_backend/features/index/vector_service.py @@ -139,7 +139,7 @@ def _suppress_stdout_only(): def _log_model_loading_once(model_name: str) -> None: """Emit a single INFO line per process/model; subsequent attempts are DEBUG only.""" - key_hash = hashlib.sha1(str(model_name or "").encode("utf-8", errors="ignore")).hexdigest()[:16] + key_hash = hashlib.sha256(str(model_name or "").encode("utf-8", errors="ignore")).hexdigest()[:16] env_key = f"MJR_AM_MODEL_LOAD_LOGGED_{key_hash}" if str(os.environ.get(env_key) or "").strip() == "1": logger.debug("Reusing previously logged model load event for '%s'", model_name) @@ -585,17 +585,22 @@ class VectorService: hf_logging.set_verbosity_error() try: self._patch_global_siglip_config_hidden_size() + # Model refs are user-configurable and may be local paths. if verbose: - self._siglip_processor = AutoProcessor.from_pretrained(self._model_name, use_fast=False) - self._siglip_model = AutoModel.from_pretrained(self._model_name) + self._siglip_processor = AutoProcessor.from_pretrained( # nosec B615 + self._model_name, use_fast=False + ) + self._siglip_model = AutoModel.from_pretrained(self._model_name) # nosec B615 else: with warnings.catch_warnings(), _suppress_stdout_only(): warnings.filterwarnings( "ignore", message=r".*huggingface_hub.*cache-system uses symlinks.*", ) - self._siglip_processor = AutoProcessor.from_pretrained(self._model_name, use_fast=False) - self._siglip_model = AutoModel.from_pretrained(self._model_name) + self._siglip_processor = AutoProcessor.from_pretrained( # nosec B615 + self._model_name, use_fast=False + ) + self._siglip_model = AutoModel.from_pretrained(self._model_name) # nosec B615 finally: hf_logging.set_verbosity(previous_hf_verbosity) @@ -1347,11 +1352,11 @@ class VectorService: } if use_fast is not None: kwargs["use_fast"] = use_fast - return AutoProcessor.from_pretrained(prompt_model_name, **kwargs) + return AutoProcessor.from_pretrained(prompt_model_name, **kwargs) # nosec B615 def _load_prompt_model_with_compat() -> Any: try: - return AutoModelForCausalLM.from_pretrained( + return AutoModelForCausalLM.from_pretrained( # nosec B615 prompt_model_name, trust_remote_code=True, attn_implementation="eager", @@ -1377,7 +1382,7 @@ class VectorService: setattr(cls, "_supports_sdpa", False) except Exception: pass - return AutoModelForCausalLM.from_pretrained( + return AutoModelForCausalLM.from_pretrained( # nosec B615 prompt_model_name, trust_remote_code=True, attn_implementation="eager", @@ -1451,23 +1456,24 @@ class VectorService: if not verbose: hf_logging.set_verbosity_error() try: + # Model refs are user-configurable and may be local paths. if verbose: - self._video_processor = AutoProcessor.from_pretrained( + self._video_processor = AutoProcessor.from_pretrained( # nosec B615 self._video_model_name, use_fast=False, ) - self._video_model = AutoModel.from_pretrained(self._video_model_name) + self._video_model = AutoModel.from_pretrained(self._video_model_name) # nosec B615 else: with warnings.catch_warnings(), _suppress_stdout_only(): warnings.filterwarnings( "ignore", message=r".*huggingface_hub.*cache-system uses symlinks.*", ) - self._video_processor = AutoProcessor.from_pretrained( + self._video_processor = AutoProcessor.from_pretrained( # nosec B615 self._video_model_name, use_fast=False, ) - self._video_model = AutoModel.from_pretrained(self._video_model_name) + self._video_model = AutoModel.from_pretrained(self._video_model_name) # nosec B615 finally: hf_logging.set_verbosity(previous_hf_verbosity) try: