Skip to content

Commit 30a9549

Browse files
Let the non-ctypes resolvers find the desktop folder (#519)
Co-authored-by: Bernát Gábor <gaborjbernat@gmail.com>
1 parent cc97359 commit 30a9549

4 files changed

Lines changed: 48 additions & 0 deletions

File tree

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,3 +5,5 @@
55
/src/platformdirs/version.py
66
/report
77
/docs/build
8+
/.coverage
9+
/.coverage.*

docs/changelog/519.bugfix.rst

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Fix :func:`~platformdirs.user_desktop_dir` on Windows builds without ``ctypes``. ``CSIDL_DESKTOPDIRECTORY`` appeared
2+
only in the ctypes lookup table, so the registry and environment variable resolvers raised ``ValueError`` for it.

src/platformdirs/windows.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -230,6 +230,9 @@ def get_win_folder_if_csidl_name_not_env_var(csidl_name: str) -> str | None: #
230230
if csidl_name == "CSIDL_MYMUSIC":
231231
return os.path.join(os.path.normpath(os.environ["USERPROFILE"]), "Music") # ruff:ignore[os-path-join]
232232

233+
if csidl_name == "CSIDL_DESKTOPDIRECTORY":
234+
return os.path.join(os.path.normpath(os.environ["USERPROFILE"]), "Desktop") # ruff:ignore[os-path-join]
235+
233236
if csidl_name == "CSIDL_PROGRAMS":
234237
return os.path.join( # ruff:ignore[os-path-join]
235238
os.path.normpath(os.environ["APPDATA"]),
@@ -270,6 +273,7 @@ def get_win_folder_from_registry(csidl_name: str) -> str:
270273
"CSIDL_MYPICTURES": "My Pictures",
271274
"CSIDL_MYVIDEO": "My Video",
272275
"CSIDL_MYMUSIC": "My Music",
276+
"CSIDL_DESKTOPDIRECTORY": "Desktop",
273277
"CSIDL_PROGRAMS": "Programs",
274278
"CSIDL_COMMON_PROGRAMS": "Common Programs",
275279
}.get(csidl_name)

tests/test_windows.py

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
Windows,
1919
get_win_folder,
2020
get_win_folder_from_env_vars,
21+
get_win_folder_from_registry,
2122
get_win_folder_if_csidl_name_not_env_var,
2223
)
2324

@@ -111,6 +112,11 @@ def test_windows(params: dict[str, Any], func: str) -> None:
111112
assert result == expected_map[func]
112113

113114

115+
def test_user_desktop_dir() -> None:
116+
# The shared PROPS fixture skips this one, so Windows would otherwise never exercise the property.
117+
assert Windows().user_desktop_dir == os.path.normpath(_WIN_FOLDERS["CSIDL_DESKTOPDIRECTORY"])
118+
119+
114120
def test_roaming_uses_appdata(mocker: MockerFixture) -> None:
115121
mock = mocker.patch("platformdirs.windows.get_win_folder", side_effect=lambda csidl: _WIN_FOLDERS[csidl])
116122
_result = Windows(appname="foo", roaming=True).user_data_dir
@@ -154,6 +160,7 @@ def test_get_win_folder_from_env_vars_direct(
154160
pytest.param("CSIDL_MYPICTURES", "Pictures", id="pictures"),
155161
pytest.param("CSIDL_MYVIDEO", "Videos", id="video"),
156162
pytest.param("CSIDL_MYMUSIC", "Music", id="music"),
163+
pytest.param("CSIDL_DESKTOPDIRECTORY", "Desktop", id="desktop"),
157164
]
158165

159166

@@ -334,6 +341,39 @@ def test_get_win_folder_via_ctypes_null_result(mocker: MockerFixture) -> None:
334341
_cleanup_ctypes_mocks()
335342

336343

344+
def test_get_win_folder_from_registry_unknown() -> None:
345+
# The lookup table is consulted before the platform guard, so this holds off Windows too.
346+
with pytest.raises(ValueError, match="Unknown CSIDL name: CSIDL_NOT_A_FOLDER"):
347+
get_win_folder_from_registry("CSIDL_NOT_A_FOLDER")
348+
349+
350+
@pytest.mark.skipif(sys.platform == "win32", reason="on Windows the resolver reads the registry instead of raising")
351+
@pytest.mark.parametrize("csidl_name", sorted(_KNOWN_FOLDER_GUIDS))
352+
def test_get_win_folder_from_registry_knows_every_known_folder(csidl_name: str) -> None:
353+
# Reaching the platform guard proves the name is in the lookup table; a missing one raises ValueError instead.
354+
with pytest.raises(NotImplementedError):
355+
get_win_folder_from_registry(csidl_name)
356+
357+
358+
@pytest.mark.skipif(sys.platform != "win32", reason="reads the live registry")
359+
@pytest.mark.parametrize("csidl_name", sorted(_KNOWN_FOLDER_GUIDS))
360+
def test_get_win_folder_from_registry_real(csidl_name: str) -> None:
361+
assert Path(get_win_folder_from_registry(csidl_name)).is_absolute()
362+
363+
364+
@pytest.mark.parametrize("csidl_name", sorted(_KNOWN_FOLDER_GUIDS))
365+
def test_get_win_folder_from_env_vars_knows_every_known_folder(
366+
monkeypatch: pytest.MonkeyPatch, csidl_name: str
367+
) -> None:
368+
# Every folder the ctypes resolver finds must also be reachable without it; desktop was missing from both
369+
# fallbacks, so user_desktop_dir raised ValueError on a Windows build without ctypes.
370+
monkeypatch.setenv("USERPROFILE", r"C:\Users\Test")
371+
monkeypatch.setenv("APPDATA", r"C:\Users\Test\AppData\Roaming")
372+
monkeypatch.setenv("LOCALAPPDATA", r"C:\Users\Test\AppData\Local")
373+
monkeypatch.setenv("ALLUSERSPROFILE", r"C:\ProgramData")
374+
assert get_win_folder_from_env_vars(csidl_name).startswith("C:")
375+
376+
337377
def test_known_folder_guids_has_all_csidl_names() -> None:
338378
expected = {
339379
"CSIDL_APPDATA",

0 commit comments

Comments
 (0)