Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions doc/api/next_api_changes/behavior/30975-VM.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
Windows configuration directory location
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

On Windows, the default configuration and cache directories now use
``%LOCALAPPDATA%\matplotlib`` instead of ``%USERPROFILE%\.matplotlib``.
This follows Windows application data storage conventions.

The ``MPLCONFIGDIR`` environment variable can still be used to override
this default.
30 changes: 27 additions & 3 deletions lib/matplotlib/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -536,6 +536,28 @@ def _get_config_or_cache_dir(xdg_base_getter):
configdir = Path(xdg_base_getter(), "matplotlib")
except RuntimeError: # raised if Path.home() is not available
pass
elif sys.platform == 'win32':
# On Windows, prefer %LOCALAPPDATA%\matplotlib which is the proper
# location for non-roaming application data (cache and config).
# See: https://docs.microsoft.com/en-us/windows/apps/design/app-settings/store-and-retrieve-app-data
#
# However, for backwards compatibility, if the old location
# (%USERPROFILE%\.matplotlib) exists, continue using it so existing
# users don't lose their config.
try:
old_configdir = Path.home() / ".matplotlib"
if old_configdir.is_dir():
configdir = old_configdir
else:
localappdata = os.environ.get('LOCALAPPDATA')
if localappdata:
configdir = Path(localappdata) / "matplotlib"
else:
configdir = old_configdir
except RuntimeError: # raised if Path.home() is not available
localappdata = os.environ.get('LOCALAPPDATA')
if localappdata:
configdir = Path(localappdata) / "matplotlib"
else:
try:
configdir = Path.home() / ".matplotlib"
Expand Down Expand Up @@ -586,8 +608,9 @@ def get_configdir():

1. If the MPLCONFIGDIR environment variable is supplied, choose that.
2. On Linux, follow the XDG specification and look first in
``$XDG_CONFIG_HOME``, if defined, or ``$HOME/.config``. On other
platforms, choose ``$HOME/.matplotlib``.
``$XDG_CONFIG_HOME``, if defined, or ``$HOME/.config``. On Windows,
use ``%LOCALAPPDATA%\\matplotlib``. On other platforms, choose
``$HOME/.matplotlib``.
3. If the chosen directory exists and is writable, use that as the
configuration directory.
4. Else, create a temporary directory, and use it as the configuration
Expand All @@ -602,7 +625,8 @@ def get_cachedir():
Return the string path of the cache directory.

The procedure used to find the directory is the same as for
`get_configdir`, except using ``$XDG_CACHE_HOME``/``$HOME/.cache`` instead.
`get_configdir`, except using ``$XDG_CACHE_HOME``/``$HOME/.cache`` instead
on Linux. On Windows, uses ``%LOCALAPPDATA%\\matplotlib`` (same as config).
"""
return _get_config_or_cache_dir(_get_xdg_cache_dir)

Expand Down
46 changes: 46 additions & 0 deletions lib/matplotlib/tests/test_matplotlib.py
Original file line number Diff line number Diff line change
Expand Up @@ -94,3 +94,49 @@ def test_get_executable_info_timeout(mock_check_output):

with pytest.raises(matplotlib.ExecutableNotFoundError, match='Timed out'):
matplotlib._get_executable_info.__wrapped__('inkscape')


@pytest.mark.skipif(sys.platform != "win32", reason="Windows-specific test")
def test_configdir_uses_localappdata_on_windows(tmp_path):
"""Test that on Windows, config/cache dir uses LOCALAPPDATA for fresh installs."""
localappdata = tmp_path / "AppData/Local"
localappdata.mkdir(parents=True)
# Set USERPROFILE to tmp_path so the old location check finds nothing
fake_home = tmp_path / "home"
fake_home.mkdir()

proc = subprocess_run_for_testing(
[sys.executable, "-c",
"import matplotlib; print(matplotlib.get_configdir())"],
env={**os.environ, "LOCALAPPDATA": str(localappdata),
"USERPROFILE": str(fake_home), "MPLCONFIGDIR": ""},
capture_output=True, text=True, check=True)

configdir = proc.stdout.strip()
# On Windows with no existing old config, should use LOCALAPPDATA\matplotlib
assert configdir == str(localappdata / "matplotlib")


@pytest.mark.skipif(sys.platform != "win32", reason="Windows-specific test")
def test_configdir_uses_userprofile_on_windows_if_exists(tmp_path):
"""
Test that on Windows, config/cache dir uses %USERPROFILE% if .matplotlib
exists.
"""
localappdata = tmp_path / "AppData/Local"
localappdata.mkdir(parents=True)
fake_home = tmp_path / "home"
fake_home.mkdir()
old_configdir = fake_home / ".matplotlib"
old_configdir.mkdir()

proc = subprocess_run_for_testing(
[sys.executable, "-c",
"import matplotlib; print(matplotlib.get_configdir())"],
env={**os.environ, "LOCALAPPDATA": str(localappdata),
"USERPROFILE": str(fake_home), "MPLCONFIGDIR": ""},
capture_output=True, text=True, check=True)

configdir = proc.stdout.strip()
# On Windows with existing old config, should continue using it
assert configdir == str(old_configdir)
Loading