{"record":{"id":"5406bbf6a21aba4b","repo":"PaddlePaddle/PaddleOCR","slug":"destination-must-be-an-existing-directory-destin","errorCode":null,"errorMessage":"Destination must be an existing directory: {destination}","messagePattern":"Destination must be an existing directory: (.+?)","errorType":"validation","errorClass":"InvalidRequestError","httpStatus":null,"severity":"warning","filePath":"paddleocr/_api_client/_resources.py","lineNumber":147,"sourceCode":"    elif destination_path.exists() and destination_path.is_dir():\n        target = destination_path / _safe_url_basename(url_path)\n    else:\n        target = destination_path\n\n    parent = target.parent\n    if not parent.exists():\n        raise FileNotFoundError(str(parent))\n    if not parent.is_dir():\n        raise InvalidRequestError(f\"Destination parent must be a directory: {parent}\")\n    return target\n\n\ndef _require_existing_directory(destination: str) -> Path:\n    dest_dir = Path(destination)\n    if not dest_dir.exists():\n        raise FileNotFoundError(destination)\n    if not dest_dir.is_dir():\n        raise InvalidRequestError(\n            f\"Destination must be an existing directory: {destination}\"\n        )\n    return dest_dir\n\n\ndef _require_writable_target(target: Path, overwrite: bool) -> None:\n    if target.exists() and not overwrite:\n        raise InvalidRequestError(f\"Destination already exists: {target}\")\n\n\ndef _atomic_write(target: Path, content: bytes, overwrite: bool) -> None:\n    fd, temp_path = tempfile.mkstemp(\n        prefix=f\".{target.name}.tmp-\",\n        dir=str(target.parent),\n    )\n    try:\n        with os.fdopen(fd, \"wb\") as temp_file:\n            temp_file.write(content)","sourceCodeStart":129,"sourceCodeEnd":165,"githubUrl":"https://github.com/PaddlePaddle/PaddleOCR/blob/2661c7c0ef5c613e8f93c6e93b2e052399f0f854/paddleocr/_api_client/_resources.py#L129-L165","documentation":"InvalidRequestError raised by _require_existing_directory when the destination exists but is not a directory (a regular file, broken behavior for the batch helpers). The batch save functions iterate pages and write multiple files, so destination must be a directory that already exists.","triggerScenarios":"save_ocr_result_resources(result, 'results.tar') where results.tar is a file; destination points at a symlink to a file; a previously-created output placeholder file shares the intended directory name.","commonSituations":"Caller passes a full file path (copied from a save_resource call) instead of the directory; a file was accidentally created where the output directory should be; config value mixes up dir and file semantics.","solutions":["Pass the directory, not a file path, to the batch helpers","If a file occupies the name, move/remove it and create a real directory there","Use a consistent convention: directories end with the output dir; individual filenames are chosen by the helper (ocr-page-N.png, markdown image names)"],"exampleFix":"# before\nsave_ocr_result_resources(result, '/data/out/pages.zip')  # existing file\n\n# after\nsave_ocr_result_resources(result, '/data/out/pages')  # existing directory","handlingStrategy":"validation","validationCode":"from pathlib import Path\np = Path(destination)\nif p.exists() and not p.is_dir():\n    raise ValueError(f'{destination} is a file; pass a directory')\np.mkdir(parents=True, exist_ok=True)","typeGuard":"def is_directory(p: str) -> bool:\n    from pathlib import Path\n    return Path(p).is_dir()","tryCatchPattern":"try:\n    save_ocr_result_resources(result, dest)\nexcept InvalidRequestError as e:\n    if 'existing directory' in str(e):\n        raise ValueError(f'pass the output directory, not a file: {e}') from e\n    raise","preventionTips":["Adopt a convention: batch save helpers take directories, save_resource takes a full target path","Never reuse a save_resource destination string for the batch helpers"],"tags":["filesystem","directory","validation"],"backgroundTag":null,"analyzedSha":"2661c7c0ef5c613e8f93c6e93b2e052399f0f854","analyzedAt":"2026-08-14T20:17:30.180Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}