{"record":{"id":"365764d6da129143","repo":"calesthio/OpenMontage","slug":"candidate-candidate-clip-id-has-no-download-url","errorCode":null,"errorMessage":"Candidate {candidate.clip_id} has no download_url","messagePattern":"Candidate (.+?) has no download_url","errorType":"validation","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"tools/video/stock_sources/archive_org.py","lineNumber":191,"sourceCode":"            for doc in docs:\n                cand = self._hydrate_candidate(doc, filters)\n                if cand is not None:\n                    out.append(cand)\n            if out:\n                return out\n\n        return []\n\n    def download(self, candidate: Candidate, out_path: Path) -> Path:\n        \"\"\"Stream the candidate's file to `out_path`.\n\n        Same pattern as the Pexels adapter — no caching, corpus builder\n        decides.\n        \"\"\"\n        import requests  # lazy\n\n        if not candidate.download_url:\n            raise ValueError(\n                f\"Candidate {candidate.clip_id} has no download_url\"\n            )\n\n        out_path = Path(out_path)\n        out_path.parent.mkdir(parents=True, exist_ok=True)\n\n        with requests.get(\n            candidate.download_url, stream=True, timeout=300\n        ) as r:\n            r.raise_for_status()\n            with open(out_path, \"wb\") as f:\n                for chunk in r.iter_content(chunk_size=1 << 16):\n                    if chunk:\n                        f.write(chunk)\n        return out_path\n\n    # ------------------------------------------------------------------\n    # Internals","sourceCodeStart":173,"sourceCodeEnd":209,"githubUrl":"https://github.com/calesthio/OpenMontage/blob/95e1c3d0ab93482159818560f6a8c8e866b9139f/tools/video/stock_sources/archive_org.py#L173-L209","documentation":"ValueError raised by the Archive.org adapter's download() when the Candidate object has a falsy download_url. Search results can reference items whose file list did not yield a direct downloadable URL; the adapter refuses to guess (Archive.org item pages are not direct media) and fails before opening the network stream.","triggerScenarios":"A candidate produced by search whose download_url was never populated (metadata lacking a playable file), candidates constructed manually in tests/scripts with only clip_id set, or stale candidates serialized before a schema change that renamed the URL field.","commonSituations":"Pipeline code that reuses candidates across sessions after pickling; deserialized candidates from an older schema; Archive.org items with restricted or derivative-only files.","solutions":["Re-run search to get fresh candidates with populated download_url instead of reusing stale ones.","Filter candidates before download: skip any where not candidate.download_url.","If building candidates manually, always set download_url from the Archive.org file metadata (e.g. the 'url' of an ident'd file)."],"exampleFix":"# before\nfor c in candidates:\n    path = source.download(c, out)\n\n# after\nfor c in candidates:\n    if not c.download_url:\n        continue\n    path = source.download(c, out)","handlingStrategy":"validation","validationCode":"candidates = [c for c in candidates if getattr(c, \"download_url\", None)]","typeGuard":"def is_downloadable(candidate) -> bool:\n    return bool(getattr(candidate, \"download_url\", None))","tryCatchPattern":"try:\n    path = source.download(candidate, out_path)\nexcept ValueError as e:\n    if \"no download_url\" in str(e):\n        continue  # skip bad candidate in batch loop\n    raise","preventionTips":["Re-run search instead of reusing stale or hand-built candidates.","Filter on download_url truthiness before any download loop."],"tags":["stock-footage","archive-org","download","data-integrity"],"backgroundTag":null,"analyzedSha":"95e1c3d0ab93482159818560f6a8c8e866b9139f","analyzedAt":"2026-08-15T06:31:20.014Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}