{"id":"fa53dd1f0c57a85f","repo":"pypa/pip","slug":"can-t-change-extraction-path-files-already-extrac","errorCode":null,"errorMessage":"Can't change extraction path, files already extracted","messagePattern":"Can't change extraction path, files already extracted","errorType":"exception","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"src/pip/_vendor/pkg_resources/__init__.py","lineNumber":1507,"sourceCode":"\n        If you do not call this routine before any extractions take place, the\n        path defaults to the return value of ``get_default_cache()``.  (Which\n        is based on the ``PYTHON_EGG_CACHE`` environment variable, with various\n        platform-specific fallbacks.  See that routine's documentation for more\n        details.)\n\n        Resources are extracted to subdirectories of this path based upon\n        information given by the ``IResourceProvider``.  You may set this to a\n        temporary directory, but then you must call ``cleanup_resources()`` to\n        delete the extracted files when done.  There is no guarantee that\n        ``cleanup_resources()`` will be able to remove all extracted files.\n\n        (Note: you may not change the extraction path for a given resource\n        manager once resources have been extracted, unless you first call\n        ``cleanup_resources()``.)\n        \"\"\"\n        if self.cached_files:\n            raise ValueError(\"Can't change extraction path, files already extracted\")\n\n        self.extraction_path = path\n\n    def cleanup_resources(self, force: bool = False) -> list[str]:\n        \"\"\"\n        Delete all extracted resource files and directories, returning a list\n        of the file and directory names that could not be successfully removed.\n        This function does not have any concurrency protection, so it should\n        generally only be called when the extraction path is a temporary\n        directory exclusive to a single process.  This method is not\n        automatically called; you must call it explicitly or register it as an\n        ``atexit`` function if you wish to ensure cleanup of a temporary\n        directory used for extractions.\n        \"\"\"\n        # XXX\n        return []\n\n","sourceCodeStart":1489,"sourceCodeEnd":1525,"githubUrl":"https://github.com/pypa/pip/blob/d7d0d0a39494e28ec1c407bd0680e4a4d1067791/src/pip/_vendor/pkg_resources/__init__.py#L1489-L1525","documentation":"Raised by ResourceManager.set_extraction_path() when you attempt to change the cache extraction directory after resources have already been extracted during the current process. The manager caches extracted files under self.cached_files; once that set is non-empty, moving the base path would orphan already-extracted files, so it refuses. You must call cleanup_resources() first to clear the cache before repointing the path.","triggerScenarios":"Calling pkg_resources.set_extraction_path(new_dir) after any earlier call to resource_filename() / get_resource_filename() on a zip/egg-backed provider caused extraction into the old directory.","commonSituations":"Setting a custom extraction path late in a long-running process, after plugins or dependencies already triggered extraction from the default temp location. Also in test harnesses that reconfigure the extraction path between tests without resetting state.","solutions":["Call pkg_resources.cleanup_resources() before set_extraction_path() to clear cached_files and allow the change.","Set the extraction path once at process startup, before any resource access that would trigger extraction.","If you need multiple extraction roots, instantiate a fresh ResourceManager rather than reusing the module-level one."],"exampleFix":"// before\npkg_resources.resource_filename('pkg', 'data.bin')  # triggers extraction\npkg_resources.set_extraction_path('/new/cache')        # ValueError\n\n// after\npkg_resources.cleanup_resources()\npkg_resources.set_extraction_path('/new/cache')","handlingStrategy":"validation","validationCode":"import pkg_resources\n\ndef safe_set_extraction_path(new_path):\n    mgr = pkg_resources._namespace_handlers  # not the manager; use the global fn\n    # the global resource manager tracks cached_files\n    import pkg_resources as pr\n    rm = pr.__dict__.get('_manager')\n    if rm is not None and getattr(rm, 'cached_files', None):\n        pr.cleanup_resources()\n    pr.set_extraction_path(new_path)","typeGuard":"# state guard: check the manager's cache before repointing\nimport pkg_resources\n\ndef extraction_path_changeable():\n    rm = getattr(pkg_resources, '_resource_manager', None)\n    return not (rm and getattr(rm, 'cached_files', None))","tryCatchPattern":"try:\n    pkg_resources.set_extraction_path(new_path)\nexcept ValueError as e:\n    if 'already extracted' in str(e):\n        pkg_resources.cleanup_resources()\n        pkg_resources.set_extraction_path(new_path)\n    else:\n        raise","preventionTips":["Call set_extraction_path() once at startup before any resource access.","Call cleanup_resources() before changing the extraction path.","Avoid reconfiguring the extraction path mid-process."],"tags":["pkg-resources","resource-manager","extraction","state"],"analyzedSha":"d7d0d0a39494e28ec1c407bd0680e4a4d1067791","analyzedAt":"2026-08-04T20:55:04.259Z","schemaVersion":2}