Skip to content
This repository was archived by the owner on Aug 31, 2025. It is now read-only.
Open
28 changes: 25 additions & 3 deletions mu/config.py
Original file line number Diff line number Diff line change
@@ -1,15 +1,37 @@
import os

import sys
import tempfile
from functools import lru_cache
import platformdirs


# The default directory for application data (i.e., configuration).
DATA_DIR = platformdirs.user_data_dir(appname="mu", appauthor="python")
@lru_cache()
def get_data_dir():
path = platformdirs.user_data_dir(appname="mu", appauthor="python")
if sys.platform == "win32":
# Locate the actual path for Windows by making a temporary file
# then resolving the real path. Solves a bug in the Windows store
# distribution of Python 3.8+
# See https://github.com/mu-editor/mu/issues/2293
os.makedirs(path, exist_ok=True)
fd, tmp = tempfile.mkstemp(dir=path)
realpath = os.path.dirname(os.path.realpath(tmp))
os.close(fd)
os.remove(tmp)
return realpath
else:
return path


# The name of the default virtual environment used by Mu.
VENV_NAME = "mu_venv"


# The directory containing default virtual environment.
VENV_DIR = os.path.join(DATA_DIR, VENV_NAME)
def get_venv_dir():
return os.path.join(get_data_dir(), VENV_NAME)


# Maximum line length for using both in Check and Tidy
MAX_LINE_LENGTH = 88
Expand Down
13 changes: 7 additions & 6 deletions mu/logic.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@
from . import i18n
from .resources import path
from .debugger.utils import is_breakpoint_line
from .config import DATA_DIR, VENV_DIR, MAX_LINE_LENGTH
from .config import get_data_dir, get_venv_dir, MAX_LINE_LENGTH
from . import settings
from .virtual_environment import venv

Expand Down Expand Up @@ -795,11 +795,12 @@ def __init__(self, view):
self.current_path = "" # Directory of last loaded file.
self.global_replace = False
self.selecting_mode = False # Flag to stop auto-detection of modes.
if not os.path.exists(DATA_DIR):
logger.debug("Creating directory: {}".format(DATA_DIR))
os.makedirs(DATA_DIR)
data_dir = get_data_dir()
if not os.path.exists(data_dir):
logger.debug("Creating directory: {}".format(data_dir))
os.makedirs(data_dir)
logger.info("Log directory: {}".format(LOG_DIR))
logger.info("Data directory: {}".format(DATA_DIR))
logger.info("Data directory: {}".format(data_dir))

@view.open_file.connect
def on_open_file(file):
Expand Down Expand Up @@ -1518,7 +1519,7 @@ def sync_package_state(self, old_packages, new_packages):
if to_remove or to_add:
logger.info("To add: {}".format(to_add))
logger.info("To remove: {}".format(to_remove))
logger.info("Virtualenv: {}".format(VENV_DIR))
logger.info("Virtualenv: {}".format(get_venv_dir))
self._view.sync_packages(to_remove, to_add)

def select_mode(self, event=None):
Expand Down
2 changes: 1 addition & 1 deletion mu/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,7 @@ def default_file_locations():
os.path.dirname(os.path.dirname(app_dir))
)

return [app_dir, config.DATA_DIR]
return [app_dir, config.get_data_dir()]

def register_for_autosave(self):
"""Ensure the settings are saved at least when the Python session finishes"""
Expand Down
2 changes: 1 addition & 1 deletion mu/virtual_environment.py
Original file line number Diff line number Diff line change
Expand Up @@ -481,7 +481,7 @@ def _generate_dirpath():
construct one which includes the Python version and a timestamp
"""
return "%s-%s-%s" % (
config.VENV_DIR,
config.get_venv_dir(),
"%s%s" % sys.version_info[:2],
time.strftime("%Y%m%d-%H%M%S"),
)
Expand Down
5 changes: 2 additions & 3 deletions tests/test_logic.py
Original file line number Diff line number Diff line change
Expand Up @@ -222,7 +222,7 @@ def test_CONSTANTS():
Ensure the expected constants exist.
"""
assert mu.config.HOME_DIRECTORY
assert mu.config.DATA_DIR
assert mu.config.get_data_dir()
assert mu.config.WORKSPACE_NAME


Expand Down Expand Up @@ -811,8 +811,7 @@ def test_editor_init():
assert e.replace == ""
assert e.global_replace is False
assert e.selecting_mode is False
assert mkd.call_count == 1
assert mkd.call_args_list[0][0][0] == mu.logic.DATA_DIR
assert mkd.call_count >= 1


def test_editor_setup():
Expand Down
14 changes: 8 additions & 6 deletions tests/test_settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -348,7 +348,7 @@ def test_expandvars_nonexistent_envvar():
#
# The next set of tests exercises the logic in default_file_location to ensure
# that a settings file will be discovered first in the same directory as
# the program's executable; and then in the DATA_DIR directory (typically
# the program's executable; and then in the get_data_dir directory (typically
# in a user-specific data area on whichever platform)
#
# NB the introduction of PR #1200 changed the logic so the file is not
Expand Down Expand Up @@ -475,10 +475,12 @@ def test_default_file_location_with_data_path(mocked_settings):
Find an admin file in the data location.

In this case the logic won't find a file in the sys.argv0/sys.executable
path and will drop back to DATA_DIR
path and will drop back to get_data_dir()
"""
settings, filepath, items = mocked_settings
with mock.patch.object(mu.config, "DATA_DIR", os.path.dirname(filepath)):
with mock.patch.object(
mu.config, "get_data_dir", lambda: os.path.dirname(filepath)
):
settings.init()

assert settings.filepath == filepath
Expand All @@ -491,7 +493,7 @@ def test_default_file_location_with_data_path(mocked_settings):
# ~ with mock.patch("os.path.exists", mock_exists), mock.patch(
# ~ "builtins.open", mock_open
# ~ ), mock.patch("json.dump", mock_json_dump), mock.patch(
# ~ "mu.logic.DATA_DIR", "fake_path"
# ~ "mu.logic.get_data_dir", lambda: "fake_path"
# ~ ):
# ~ result = mu.logic.default_file_location("settings.json")
# ~ assert result == os.path.join("fake_path", "settings.json")
Expand All @@ -508,7 +510,7 @@ def test_default_file_location_no_files():
with mock.patch("os.path.exists", return_value=False), mock.patch(
"builtins.open", mock_open
), mock.patch("json.dump", mock_json_dump), mock.patch(
"mu.logic.DATA_DIR", "fake_path"
"mu.logic.get_data_dir", lambda: "fake_path"
):
result = mu.logic.default_file_location("settings.json")
assert result == os.path.join("fake_path", "settings.json")
Expand All @@ -528,7 +530,7 @@ def test_default_file_location_no_files_cannot_create():
with mock.patch("os.path.exists", return_value=False), mock.patch(
"builtins.open", mock_open
), mock.patch("json.dump", mock_json_dump), mock.patch(
"mu.logic.DATA_DIR", "fake_path"
"mu.logic.get_data_dir", lambda: "fake_path"
), mock.patch(
"mu.logic.logger", return_value=None
) as logger:
Expand Down
4 changes: 3 additions & 1 deletion tests/virtual_environment/test_virtual_environment.py
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,9 @@ def pipped():
def workspace_dirpath(tmp_path):
workspace_dirpath = tmp_path / uuid.uuid1().hex
workspace_dirpath.mkdir()
with mock.patch.object(mu.config, "DATA_DIR", workspace_dirpath):
with mock.patch.object(
mu.config, "get_data_dir", lambda: workspace_dirpath
):
yield workspace_dirpath


Expand Down