Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 0984d6d

Browse files
authored
fix: Use LOCALAPPDATA for config/cache directories on Windows (#30975)
On Windows, the default configuration and cache directories now use `%LOCALAPPDATA%\matplotlib` instead of `%USERPROFILE%\.matplotlib`, following Windows application data storage conventions. This PR adds backwards compatibility by checking if `%USERPROFILE%\.matplotlib` already exists before using the new location. Closes #20779
1 parent b83305e commit 0984d6d

3 files changed

Lines changed: 82 additions & 3 deletions

File tree

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
Windows configuration directory location
2+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
3+
4+
On Windows, the default configuration and cache directories now use
5+
``%LOCALAPPDATA%\matplotlib`` instead of ``%USERPROFILE%\.matplotlib``.
6+
This follows Windows application data storage conventions.
7+
8+
The ``MPLCONFIGDIR`` environment variable can still be used to override
9+
this default.

lib/matplotlib/__init__.py

Lines changed: 27 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -536,6 +536,28 @@ def _get_config_or_cache_dir(xdg_base_getter):
536536
configdir = Path(xdg_base_getter(), "matplotlib")
537537
except RuntimeError: # raised if Path.home() is not available
538538
pass
539+
elif sys.platform == 'win32':
540+
# On Windows, prefer %LOCALAPPDATA%\matplotlib which is the proper
541+
# location for non-roaming application data (cache and config).
542+
# See: https://docs.microsoft.com/en-us/windows/apps/design/app-settings/store-and-retrieve-app-data
543+
#
544+
# However, for backwards compatibility, if the old location
545+
# (%USERPROFILE%\.matplotlib) exists, continue using it so existing
546+
# users don't lose their config.
547+
try:
548+
old_configdir = Path.home() / ".matplotlib"
549+
if old_configdir.is_dir():
550+
configdir = old_configdir
551+
else:
552+
localappdata = os.environ.get('LOCALAPPDATA')
553+
if localappdata:
554+
configdir = Path(localappdata) / "matplotlib"
555+
else:
556+
configdir = old_configdir
557+
except RuntimeError: # raised if Path.home() is not available
558+
localappdata = os.environ.get('LOCALAPPDATA')
559+
if localappdata:
560+
configdir = Path(localappdata) / "matplotlib"
539561
else:
540562
try:
541563
configdir = Path.home() / ".matplotlib"
@@ -586,8 +608,9 @@ def get_configdir():
586608
587609
1. If the MPLCONFIGDIR environment variable is supplied, choose that.
588610
2. On Linux, follow the XDG specification and look first in
589-
``$XDG_CONFIG_HOME``, if defined, or ``$HOME/.config``. On other
590-
platforms, choose ``$HOME/.matplotlib``.
611+
``$XDG_CONFIG_HOME``, if defined, or ``$HOME/.config``. On Windows,
612+
use ``%LOCALAPPDATA%\\matplotlib``. On other platforms, choose
613+
``$HOME/.matplotlib``.
591614
3. If the chosen directory exists and is writable, use that as the
592615
configuration directory.
593616
4. Else, create a temporary directory, and use it as the configuration
@@ -602,7 +625,8 @@ def get_cachedir():
602625
Return the string path of the cache directory.
603626
604627
The procedure used to find the directory is the same as for
605-
`get_configdir`, except using ``$XDG_CACHE_HOME``/``$HOME/.cache`` instead.
628+
`get_configdir`, except using ``$XDG_CACHE_HOME``/``$HOME/.cache`` instead
629+
on Linux. On Windows, uses ``%LOCALAPPDATA%\\matplotlib`` (same as config).
606630
"""
607631
return _get_config_or_cache_dir(_get_xdg_cache_dir)
608632

lib/matplotlib/tests/test_matplotlib.py

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,3 +94,49 @@ def test_get_executable_info_timeout(mock_check_output):
9494

9595
with pytest.raises(matplotlib.ExecutableNotFoundError, match='Timed out'):
9696
matplotlib._get_executable_info.__wrapped__('inkscape')
97+
98+
99+
@pytest.mark.skipif(sys.platform != "win32", reason="Windows-specific test")
100+
def test_configdir_uses_localappdata_on_windows(tmp_path):
101+
"""Test that on Windows, config/cache dir uses LOCALAPPDATA for fresh installs."""
102+
localappdata = tmp_path / "AppData/Local"
103+
localappdata.mkdir(parents=True)
104+
# Set USERPROFILE to tmp_path so the old location check finds nothing
105+
fake_home = tmp_path / "home"
106+
fake_home.mkdir()
107+
108+
proc = subprocess_run_for_testing(
109+
[sys.executable, "-c",
110+
"import matplotlib; print(matplotlib.get_configdir())"],
111+
env={**os.environ, "LOCALAPPDATA": str(localappdata),
112+
"USERPROFILE": str(fake_home), "MPLCONFIGDIR": ""},
113+
capture_output=True, text=True, check=True)
114+
115+
configdir = proc.stdout.strip()
116+
# On Windows with no existing old config, should use LOCALAPPDATA\matplotlib
117+
assert configdir == str(localappdata / "matplotlib")
118+
119+
120+
@pytest.mark.skipif(sys.platform != "win32", reason="Windows-specific test")
121+
def test_configdir_uses_userprofile_on_windows_if_exists(tmp_path):
122+
"""
123+
Test that on Windows, config/cache dir uses %USERPROFILE% if .matplotlib
124+
exists.
125+
"""
126+
localappdata = tmp_path / "AppData/Local"
127+
localappdata.mkdir(parents=True)
128+
fake_home = tmp_path / "home"
129+
fake_home.mkdir()
130+
old_configdir = fake_home / ".matplotlib"
131+
old_configdir.mkdir()
132+
133+
proc = subprocess_run_for_testing(
134+
[sys.executable, "-c",
135+
"import matplotlib; print(matplotlib.get_configdir())"],
136+
env={**os.environ, "LOCALAPPDATA": str(localappdata),
137+
"USERPROFILE": str(fake_home), "MPLCONFIGDIR": ""},
138+
capture_output=True, text=True, check=True)
139+
140+
configdir = proc.stdout.strip()
141+
# On Windows with existing old config, should continue using it
142+
assert configdir == str(old_configdir)

0 commit comments

Comments
 (0)