Search code examples
pythonoverwritehardlink

Python - overwrite a link if it exists


from os import link

link('WInd_Rose_Aguiar.svg', 'Wikipedia Daily Featured Picture')

# A day has passed

link('Piero_del_Pollaiuolo_-_Profile_Portrait_of_a_Young_Lady_-_Gemäldegalerie_Berlin_-_Google_Art_Project.jpg',
     'Wikipedia Daily Featured Picture') # Exception

The results of calling the above script:

my@comp:~/wtfdir$ python3 wtf.py
Traceback (most recent call last):
  File "wtf.py", line 8, in <module>
    'Wikipedia Daily Featured Picture') # Exception
FileExistsError: [Errno 17] File exists: 'Piero_del_Pollaiuolo_-_Profile_Portrait_of_a_Young_Lady_-_Gemäldegalerie_Berlin_-_Google_Art_Project.jpg' -> 'Wikipedia Daily Featured Picture'

Creating the first link succeeds. Creating the second one fails.

That's hardly what I would expect… My intention is to overwrite this link.

https://docs.python.org/3/library/os.html#os.link ⇐ I can’t see a force or overwrite_if_exists or similar parameter to the function link in the docs.

How can I create a link pointing to a new source, overwriting the previous link if it exists?

Well yes – I guess I can do sth like this:

from os import link, remove
from os.path import isfile

def force_link(src, dest):
    if isfile(dest):
        remove(dest)
    link(src, dest)

force_link('WInd_Rose_Aguiar.svg', 'Wikipedia Daily Featured Picture')

# A day has passed

force_link('Piero_del_Pollaiuolo_-_Profile_Portrait_of_a_Young_Lady_-_Gemäldegalerie_Berlin_-_Google_Art_Project.jpg',
     'Wikipedia Daily Featured Picture') # No longer exception

But this is cumbersome and at least in theory may be incorrect (what if some other process re-creates the file between remove(dest) and link(src, dest)?). And while perhaps this (at least theoretical) incorrectness could be resolved, the resulting code would be even more cumbersome, I guess…

There must be a better, more right-handed way to do this!


Solution

  • Create a new link for the file you want to expose. Then replace your fixed link with the new link you just created.

    from tempfile import TemporaryDirectory
    
    
    def force_link(src, dest):
        with TemporaryDirectory(dir=os.path.dirname(dest)) as d:
            tmpname = os.path.join(d, "foo")
            os.link(src, tmpname)
            os.replace(tmpname, dest)
    

    You may need to ensure that the permissions on dest are correct afterwards.

    os.link will succeed in securely creating a new link in the temporary directory. Then you'll use os.replace to securely rename the temporary link to dest, effectively overwriting the old link with the new.