34

I already found this question that suggests to use os.path.expanduser(path) to get the user's home directory.

I would like to achieve the same with the "Downloads" folder. I know that this is possible in C#, yet I'm new to Python and don't know if this is possible here too, preferable platform-independent (Windows, Ubuntu).

I know that I just could do download_folder = os.path.expanduser("~")+"/Downloads/", yet (at least in Windows) it is possible to change the Default download folder.

Community
  • 1
  • 1
Markus Weninger
  • 10,693
  • 6
  • 57
  • 121
  • 2
    With sufficient ctypes-foo you could adapt [the Windows-specific code in this answer](http://stackoverflow.com/a/7672816/1600898) to Python (with a fallback to `os.expanduser(...)` on non-Windows platforms). [Here](http://stackoverflow.com/a/29888752/1600898) is an example. Note, however, that a "Downloads" directory is not a platform-independent concept. It is quite possible to encounter Linux systems that don't have one, so be sure to create it if it doesn't exist. – user4815162342 Mar 07 '16 at 18:41
  • @user4815162342: Okay, thanks for the advice with `a "Downloads" directory is not a platform-independent concept.`! – Markus Weninger Mar 07 '16 at 18:43

4 Answers4

35
from pathlib import Path
downloads_path = str(Path.home() / "Downloads")
Shmidt
  • 16,230
  • 18
  • 85
  • 132
  • 2
    I am pretty sure this does not account for relocation on Windows as per the OP. In fact, I am pretty sure that Windows does not actually require the Downloads directory to be located within anything that looks like a "home" directory. – Karl Knechtel Jan 29 '22 at 04:36
22

This fairly simple solution (expanded from this reddit post) worked for me

import os

def get_download_path():
    """Returns the default downloads path for linux or windows"""
    if os.name == 'nt':
        import winreg
        sub_key = r'SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\Shell Folders'
        downloads_guid = '{374DE290-123F-4565-9164-39C4925E467B}'
        with winreg.OpenKey(winreg.HKEY_CURRENT_USER, sub_key) as key:
            location = winreg.QueryValueEx(key, downloads_guid)[0]
        return location
    else:
        return os.path.join(os.path.expanduser('~'), 'downloads')
  • The GUID can be obtained from Microsoft's KNOWNFOLDERID docs
  • This can be expanded to work more generically other directories
Alexander McFarlane
  • 9,832
  • 8
  • 52
  • 96
  • 1
    But this would find the downloads folder path of a local server. How do you find the downloads folder of the client if the same is to be acheived for a web app? – user10058776 Jun 08 '21 at 20:54
  • 1
    you cannot do this server side for obvious reasons - you will need to embed some kind of script in your web app that is ran by the end user's browser – Alexander McFarlane Jun 11 '21 at 15:25
12

Correctly locating Windows folders is somewhat of a chore in Python. According to answers covering Microsoft development technologies, such as this one, they should be obtained using the Vista Known Folder API. This API is not wrapped by the Python standard library (though there is an issue from 2008 requesting it), but one can use the ctypes module to access it anyway.

Adapting the above answer to use the folder id for downloads shown here and combining it with your existing Unix code should result in code that looks like this:

import os

if os.name == 'nt':
    import ctypes
    from ctypes import windll, wintypes
    from uuid import UUID

    # ctypes GUID copied from MSDN sample code
    class GUID(ctypes.Structure):
        _fields_ = [
            ("Data1", wintypes.DWORD),
            ("Data2", wintypes.WORD),
            ("Data3", wintypes.WORD),
            ("Data4", wintypes.BYTE * 8)
        ] 

        def __init__(self, uuidstr):
            uuid = UUID(uuidstr)
            ctypes.Structure.__init__(self)
            self.Data1, self.Data2, self.Data3, \
                self.Data4[0], self.Data4[1], rest = uuid.fields
            for i in range(2, 8):
                self.Data4[i] = rest>>(8-i-1)*8 & 0xff

    SHGetKnownFolderPath = windll.shell32.SHGetKnownFolderPath
    SHGetKnownFolderPath.argtypes = [
        ctypes.POINTER(GUID), wintypes.DWORD,
        wintypes.HANDLE, ctypes.POINTER(ctypes.c_wchar_p)
    ]

    def _get_known_folder_path(uuidstr):
        pathptr = ctypes.c_wchar_p()
        guid = GUID(uuidstr)
        if SHGetKnownFolderPath(ctypes.byref(guid), 0, 0, ctypes.byref(pathptr)):
            raise ctypes.WinError()
        return pathptr.value

    FOLDERID_Download = '{374DE290-123F-4565-9164-39C4925E467B}'

    def get_download_folder():
        return _get_known_folder_path(FOLDERID_Download)
else:
    def get_download_folder():
        home = os.path.expanduser("~")
        return os.path.join(home, "Downloads")

A more complete module for retrieving known folders from Python is available on github.

Community
  • 1
  • 1
user4815162342
  • 124,516
  • 15
  • 228
  • 298
  • Thanks for the detailed investigation and the link to the issue. – Markus Weninger Mar 07 '16 at 19:26
  • 1
    @MarkusWeninger You might also want to take a look at [this more complete wrapper](https://gist.github.com/mkropat/7550097) for `SHGetKnownFolderPath`. – user4815162342 Mar 08 '16 at 16:05
  • Thanks! I will look into it. `get_download_folder()` currently crashes with the following error. `File ".\downloadfolders.py", line 34, in _get_known_folder_path if SHGetKnownFolderPath(ctypes.byref(guid), 0, 0, ctypes.byref(pathptr)): ctypes.ArgumentError: argument 4: : expected LP_c_char_p instance instead of pointer to c_wchar_p` – Markus Weninger Mar 08 '16 at 16:07
  • Yes, `print(get_download_folder())` now prints my download folder (using Windows 10). Thanks a lot again, a pitty I cannot upvote again ;) – Markus Weninger Mar 08 '16 at 16:39
  • @MarkusWeninger You're welcome. Answer amended to remove the "untested" claim. :) – user4815162342 Mar 09 '16 at 07:22
12

For python3+ mac or linux

from pathlib import Path
path_to_download_folder = str(os.path.join(Path.home(), "Downloads"))
Jeff WR
  • 152
  • 1
  • 3