Skip to content

Support moving across filesystems in pathlib.Path, as shutil.move() does #73991

Description

@LaurentMazuel
BPO 29805
Nosy @brettcannon, @pfmoore, @ericvsmith, @tjguk, @zware, @eryksun, @zooba

Note: these values reflect the state of the issue at the time it was migrated and might not reflect the current state.

Show more details

GitHub fields:

assignee = None
closed_at = None
created_at = <Date 2017-03-13.21:03:44.694>
labels = ['3.8', 'type-feature', 'library', '3.9', '3.10']
title = 'Support moving across filesystems in pathlib.Path, as shutil.move() does'
updated_at = <Date 2021-03-15.21:26:30.922>
user = 'https://bugs.python.org/LaurentMazuel'

bugs.python.org fields:

activity = <Date 2021-03-15.21:26:30.922>
actor = 'eryksun'
assignee = 'none'
closed = False
closed_date = None
closer = None
components = ['Library (Lib)']
creation = <Date 2017-03-13.21:03:44.694>
creator = 'Laurent.Mazuel'
dependencies = []
files = []
hgrepos = []
issue_num = 29805
keywords = []
message_count = 6.0
messages = ['289549', '289552', '289559', '289630', '289687', '289688']
nosy_count = 8.0
nosy_names = ['brett.cannon', 'paul.moore', 'eric.smith', 'tim.golden', 'Laurent.Mazuel', 'zach.ware', 'eryksun', 'steve.dower']
pr_nums = []
priority = 'normal'
resolution = None
stage = None
status = 'open'
superseder = None
type = 'enhancement'
url = 'https://bugs.python.org/issue29805'
versions = ['Python 3.8', 'Python 3.9', 'Python 3.10']

Linked PRs

Activity

  1. LaurentMazuel commented on Mar 13, 2017

    LaurentMazuelmannequin
    MannequinAuthor

    Trying to use Pathlib and Path.replace on Windows if drive are different leads to an issue:

      File "D:\\myscript.py", line 184, in update
        client_generated_path.replace(destination_folder)
      File "c:\\program files (x86)\\python35-32\\Lib\\pathlib.py", line 1273, in replace
        self.\_accessor.replace(self, target)
      File "c:\\program files (x86)\\python35-32\\Lib\\pathlib.py", line 377, in wrapped
        return strfunc(str(pathobjA), str(pathobjB), \*args)
    OSError: [WinError 17] The system cannot move the file to a different disk drive: 'C:\\\\MyFolder' -\> 'D:\\\\MyFolderNewName'
    

    This is a known situation of os.rename, and workaround I found is to use shutil or to copy/delete manually in two steps (e.g. http://stackoverflow.com/questions/21116510/python-oserror-winerror-17-the-system-cannot-move-the-file-to-a-different-d)

    When using Pathlib, it's not that easy to workaround using shutil (even if thanks to Brett Cannon now shutil accepts Path in Py3.6, not everybody has Py3.6). At least this should be documented with a recommendation for that situation. I love Pathlib and it's too bad my code becomes complicated when it was so simple :(

  2. added
    stdlibStandard Library Python modules in the Lib/ directory
    and removed on Mar 13, 2017
  3. LaurentMazuel commented on Mar 13, 2017

    LaurentMazuelmannequin
    MannequinAuthor

    Just to confirm, I was able to workaround it with Py3.6:

        # client_generated_path.replace(destination_folder)
        shutil.move(client_generated_path, destination_folder)

    It is reasonable to think about adding a similar feature to pathlib if it doesn't support it and just special-case the drive-to-drive scenario for Windows

  4. eryksun commented on Mar 14, 2017

    @eryksun
    Contributor

    Moving a file across volumes isn't atomic. Getting an exception in this case can be useful. There could be a "strict" keyword-only parameter that defaults to False. If it's true, then replace() won't try to move the file.

  5. ericvsmith commented on Mar 15, 2017

    @ericvsmith
    Member

    I agree this needs to be different from replace(), due to not being atomic. That makes it an enhancement, so I'm removing 3.5.

    I'm +1 on Path supporting something like shutil.move().

  6. brettcannon commented on Mar 15, 2017

    @brettcannon
    Member

    I also support the idea of getting something like shutil.move() into pathlib.

  7. brettcannon commented on Mar 15, 2017

    @brettcannon
    Member

    I should also mention that rename() (https://docs.python.org/3/library/pathlib.html#pathlib.Path.rename) and replace() (https://docs.python.org/3/library/pathlib.html#pathlib.Path.replace) already do exist, so it might be best to add a keyword-only flag to one of those for this use-case.

  8. 145 remaining items

  9. added 5 commits that reference this issue on Aug 26, 2024
  10. barneygale commented on Aug 26, 2024

    @barneygale
    Contributor

    It's done! Thank you everyone who helped formulate and review this feature.

    We've added new copy(), copy_into(), move() and move_into() methods, which work on both files and directories. The interfaces are refined to pathlib standards, with certain nonessential functionality (like support for filtering in copy()) omitted for now. Users may wish to propose new capabilities on the ideas forum.

    The new methods expect exact targets (or target directories for the _into() variants), whereas shutil checks for existing directories and adjusts the target as necessary. The latter behaviour is helpful in an interactive shell with tab-complete, but less so in a stored program where state of the filesystem might not be known (for example, shutil.copy('a', 'b') could create either b or b/a).

    The copy() and copy_into() methods use fcntl.FICLONE or os.copy_file_range where available (see GH-81338, GH-81340). This work has stalled for years in shutil for backwards-compatibility concerns, happily not present here.

    Feedback most welcome! We have ~9 months until 3.14 beta 1 where the interface is more locked down, so there's still plenty of time make changes. I'll close this issue, but I can re-open it (or log new issues) if problems are identified. Thanks again, all.

  11. encukou commented on Dec 2, 2024

    @encukou
    Member

    For copying trees, what happens on error -- for example, when one of the files has a name that's incompatible with the target filesystem?

  12. barneygale commented on Dec 2, 2024

    @barneygale
    Contributor

    In that case you'll get an OSError. Any copies already made will be left on disk, and in the case of move(), none of the original files will be deleted.

  13. agronholm commented on Dec 6, 2024

    @agronholm
    Contributor

    It looks like, unlike copy(), this method fails if I pass a path-like object that just happens to have a method named stat(). It then fails at self._ensure_different_file(target) which already assumes a Path as its argument type. It really should do an isinstance check on the target. In my case, stat() is a coroutine method, so it fails with AttributeError: 'coroutine' object has no attribute 'st_ino'.

  14. zooba commented on Dec 9, 2024

    @zooba
    Member

    Passing an object that looks similar but isn't the same is the caller's error. An AttributeError is exactly what you should get in this case.

    That said, it probably wouldn't be unreasonable to convert any incoming paths to the same type as the source. We shouldn't type check here, we should try to convert and let the errors bubble out to the caller.

  15. agronholm commented on Dec 9, 2024

    @agronholm
    Contributor

    Such type checks are done everywhere else, including in the method in question! This would be solved simply by doing said type check earlier.

    Is it reasonable to expect this to fail on a PathLike object? How am I supposed to know what I can pass as the target then?

  16. zooba commented on Dec 9, 2024

    @zooba
    Member

    In general for pathlib, you should be able to pass any object that can be interpreted as the same kind of path as the one you're passing it to. I know that upsets the static typing people because it's hard to define, but being accepting of inputs is a key Python feature.

    In this case, that means converting the input to the same type as the target before using it. Most of the functions that have type checks are essentially doing this - they're negative checks, followed by essentially type(self)(other), which will try its hardest to parse the path out of the input. But samefile is not doing this and clearly ought to be. I suggest opening a new issue.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    stdlibStandard Library Python modules in the Lib/ directorytopic-pathlibtype-featureA feature request or enhancement

    Projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions