Addon Manager: Refactor interface to FreeCAD
This commit is contained in:
committed by
Chris Hennes
parent
519d26e13c
commit
7b590eace0
@@ -0,0 +1,306 @@
|
||||
# ***************************************************************************
|
||||
# * *
|
||||
# * Copyright (c) 2023 FreeCAD Project Association *
|
||||
# * *
|
||||
# * This file is part of FreeCAD. *
|
||||
# * *
|
||||
# * FreeCAD is free software: you can redistribute it and/or modify it *
|
||||
# * under the terms of the GNU Lesser General Public License as *
|
||||
# * published by the Free Software Foundation, either version 2.1 of the *
|
||||
# * License, or (at your option) any later version. *
|
||||
# * *
|
||||
# * FreeCAD is distributed in the hope that it will be useful, but *
|
||||
# * WITHOUT ANY WARRANTY; without even the implied warranty of *
|
||||
# * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU *
|
||||
# * Lesser General Public License for more details. *
|
||||
# * *
|
||||
# * You should have received a copy of the GNU Lesser General Public *
|
||||
# * License along with FreeCAD. If not, see *
|
||||
# * <https://www.gnu.org/licenses/>. *
|
||||
# * *
|
||||
# ***************************************************************************
|
||||
|
||||
"""Tests for the Addon Manager's FreeCAD interface classes."""
|
||||
|
||||
import json
|
||||
import os
|
||||
import sys
|
||||
import tempfile
|
||||
import unittest
|
||||
from unittest.mock import patch, MagicMock
|
||||
|
||||
# pylint: disable=protected-access,import-outside-toplevel
|
||||
|
||||
|
||||
class TestConsole(unittest.TestCase):
|
||||
"""Tests for the Console"""
|
||||
|
||||
def setUp(self) -> None:
|
||||
self.saved_freecad = None
|
||||
if "FreeCAD" in sys.modules:
|
||||
self.saved_freecad = sys.modules["FreeCAD"]
|
||||
sys.modules.pop("FreeCAD")
|
||||
if "addonmanager_freecad_interface" in sys.modules:
|
||||
sys.modules.pop("addonmanager_freecad_interface")
|
||||
sys.path.append("../../")
|
||||
|
||||
def tearDown(self) -> None:
|
||||
if "FreeCAD" in sys.modules:
|
||||
sys.modules.pop("FreeCAD")
|
||||
if self.saved_freecad is not None:
|
||||
sys.modules["FreeCAD"] = self.saved_freecad
|
||||
|
||||
def test_log_with_freecad(self):
|
||||
"""Ensure that if FreeCAD exists, the appropriate function is called"""
|
||||
sys.modules["FreeCAD"] = unittest.mock.MagicMock()
|
||||
import addonmanager_freecad_interface as fc
|
||||
|
||||
fc.Console.PrintLog("Test output")
|
||||
self.assertTrue(isinstance(fc.Console, unittest.mock.MagicMock))
|
||||
self.assertTrue(fc.Console.PrintLog.called)
|
||||
|
||||
def test_log_no_freecad(self):
|
||||
"""Test that if the FreeCAD import fails, the logger is set up correctly, and
|
||||
implements PrintLog"""
|
||||
sys.modules["FreeCAD"] = None
|
||||
with patch(
|
||||
"addonmanager_freecad_interface.logging", new=MagicMock()
|
||||
) as mock_logging:
|
||||
import addonmanager_freecad_interface as fc
|
||||
|
||||
fc.Console.PrintLog("Test output")
|
||||
self.assertTrue(isinstance(fc.Console, fc.ConsoleReplacement))
|
||||
self.assertTrue(mock_logging.log.called)
|
||||
|
||||
def test_message_no_freecad(self):
|
||||
"""Test that if the FreeCAD import fails the logger implements PrintMessage"""
|
||||
sys.modules["FreeCAD"] = None
|
||||
with patch(
|
||||
"addonmanager_freecad_interface.logging", new=MagicMock()
|
||||
) as mock_logging:
|
||||
import addonmanager_freecad_interface as fc
|
||||
|
||||
fc.Console.PrintMessage("Test output")
|
||||
self.assertTrue(mock_logging.info.called)
|
||||
|
||||
def test_warning_no_freecad(self):
|
||||
"""Test that if the FreeCAD import fails the logger implements PrintWarning"""
|
||||
sys.modules["FreeCAD"] = None
|
||||
with patch(
|
||||
"addonmanager_freecad_interface.logging", new=MagicMock()
|
||||
) as mock_logging:
|
||||
import addonmanager_freecad_interface as fc
|
||||
|
||||
fc.Console.PrintWarning("Test output")
|
||||
self.assertTrue(mock_logging.warning.called)
|
||||
|
||||
def test_error_no_freecad(self):
|
||||
"""Test that if the FreeCAD import fails the logger implements PrintError"""
|
||||
sys.modules["FreeCAD"] = None
|
||||
with patch(
|
||||
"addonmanager_freecad_interface.logging", new=MagicMock()
|
||||
) as mock_logging:
|
||||
import addonmanager_freecad_interface as fc
|
||||
|
||||
fc.Console.PrintError("Test output")
|
||||
self.assertTrue(mock_logging.error.called)
|
||||
|
||||
|
||||
class TestParameters(unittest.TestCase):
|
||||
"""Tests for the Parameters"""
|
||||
|
||||
def setUp(self) -> None:
|
||||
self.saved_freecad = None
|
||||
if "FreeCAD" in sys.modules:
|
||||
self.saved_freecad = sys.modules["FreeCAD"]
|
||||
sys.modules.pop("FreeCAD")
|
||||
if "addonmanager_freecad_interface" in sys.modules:
|
||||
sys.modules.pop("addonmanager_freecad_interface")
|
||||
sys.path.append("../../")
|
||||
|
||||
def tearDown(self) -> None:
|
||||
if "FreeCAD" in sys.modules:
|
||||
sys.modules.pop("FreeCAD")
|
||||
if self.saved_freecad is not None:
|
||||
sys.modules["FreeCAD"] = self.saved_freecad
|
||||
|
||||
def test_param_get_with_freecad(self):
|
||||
"""Ensure that if FreeCAD exists, the built-in FreeCAD function is called"""
|
||||
sys.modules["FreeCAD"] = unittest.mock.MagicMock()
|
||||
import addonmanager_freecad_interface as fc
|
||||
|
||||
prefs = fc.ParamGet("some/fake/path")
|
||||
self.assertTrue(isinstance(prefs, unittest.mock.MagicMock))
|
||||
|
||||
def test_param_get_no_freecad(self):
|
||||
"""Test that if the FreeCAD import fails, param_get returns a ParametersReplacement"""
|
||||
sys.modules["FreeCAD"] = None
|
||||
import addonmanager_freecad_interface as fc
|
||||
|
||||
prefs = fc.ParamGet("some/fake/path")
|
||||
self.assertTrue(isinstance(prefs, fc.ParametersReplacement))
|
||||
|
||||
def test_replacement_getters_and_setters(self):
|
||||
"""Test that ParameterReplacement's getters, setters, and deleters work"""
|
||||
sys.modules["FreeCAD"] = None
|
||||
import addonmanager_freecad_interface as fc
|
||||
|
||||
prf = fc.ParamGet("some/fake/path")
|
||||
gs_types = [
|
||||
("Bool", prf.GetBool, prf.SetBool, prf.RemBool, True, False),
|
||||
("Int", prf.GetInt, prf.SetInt, prf.RemInt, 42, 0),
|
||||
("Float", prf.GetFloat, prf.SetFloat, prf.RemFloat, 1.2, 3.4),
|
||||
("String", prf.GetString, prf.SetString, prf.RemString, "test", "other"),
|
||||
]
|
||||
for gs_type in gs_types:
|
||||
with self.subTest(msg=f"Testing {gs_type[0]}", gs_type=gs_type):
|
||||
getter = gs_type[1]
|
||||
setter = gs_type[2]
|
||||
deleter = gs_type[3]
|
||||
value_1 = gs_type[4]
|
||||
value_2 = gs_type[5]
|
||||
self.assertEqual(getter("test", value_1), value_1)
|
||||
self.assertEqual(getter("test", value_2), value_2)
|
||||
self.assertNotIn("test", prf.parameters)
|
||||
setter("test", value_1)
|
||||
self.assertIn("test", prf.parameters)
|
||||
self.assertEqual(getter("test", value_2), value_1)
|
||||
deleter("test")
|
||||
self.assertNotIn("test", prf.parameters)
|
||||
|
||||
|
||||
class TestDataPaths(unittest.TestCase):
|
||||
"""Tests for the data paths"""
|
||||
|
||||
def setUp(self) -> None:
|
||||
self.saved_freecad = None
|
||||
if "FreeCAD" in sys.modules:
|
||||
self.saved_freecad = sys.modules["FreeCAD"]
|
||||
sys.modules.pop("FreeCAD")
|
||||
if "addonmanager_freecad_interface" in sys.modules:
|
||||
sys.modules.pop("addonmanager_freecad_interface")
|
||||
sys.path.append("../../")
|
||||
|
||||
def tearDown(self) -> None:
|
||||
if "FreeCAD" in sys.modules:
|
||||
sys.modules.pop("FreeCAD")
|
||||
if self.saved_freecad is not None:
|
||||
sys.modules["FreeCAD"] = self.saved_freecad
|
||||
|
||||
def test_init_with_freecad(self):
|
||||
"""Ensure that if FreeCAD exists, the appropriate functions are called"""
|
||||
sys.modules["FreeCAD"] = unittest.mock.MagicMock()
|
||||
import addonmanager_freecad_interface as fc
|
||||
|
||||
data_paths = fc.DataPaths()
|
||||
self.assertTrue(sys.modules["FreeCAD"].getUserAppDataDir.called)
|
||||
self.assertTrue(sys.modules["FreeCAD"].getUserMacroDir.called)
|
||||
self.assertTrue(sys.modules["FreeCAD"].getUserCachePath.called)
|
||||
self.assertIsNotNone(data_paths.mod_dir)
|
||||
self.assertIsNotNone(data_paths.cache_dir)
|
||||
self.assertIsNotNone(data_paths.macro_dir)
|
||||
|
||||
def test_init_without_freecad(self):
|
||||
"""Ensure that if FreeCAD does not exist, the appropriate functions are called"""
|
||||
sys.modules["FreeCAD"] = None
|
||||
import addonmanager_freecad_interface as fc
|
||||
|
||||
data_paths = fc.DataPaths()
|
||||
self.assertIsNotNone(data_paths.mod_dir)
|
||||
self.assertIsNotNone(data_paths.cache_dir)
|
||||
self.assertIsNotNone(data_paths.macro_dir)
|
||||
self.assertNotEqual(data_paths.mod_dir, data_paths.cache_dir)
|
||||
self.assertNotEqual(data_paths.mod_dir, data_paths.macro_dir)
|
||||
self.assertNotEqual(data_paths.cache_dir, data_paths.macro_dir)
|
||||
|
||||
|
||||
class TestPreferences(unittest.TestCase):
|
||||
"""Tests for the preferences wrapper"""
|
||||
|
||||
def setUp(self) -> None:
|
||||
sys.path.append("../../")
|
||||
import addonmanager_freecad_interface as fc
|
||||
|
||||
self.fc = fc
|
||||
|
||||
def tearDown(self) -> None:
|
||||
pass
|
||||
|
||||
def test_load_preferences_defaults(self):
|
||||
"""Preferences are loaded from a given file"""
|
||||
defaults = self.given_defaults()
|
||||
with tempfile.TemporaryDirectory() as temp_dir:
|
||||
json_file = os.path.join(temp_dir, "defaults.json")
|
||||
with open(json_file, "w", encoding="utf-8") as f:
|
||||
f.write(json.dumps(defaults))
|
||||
self.fc.Preferences._load_preferences_defaults(json_file)
|
||||
self.assertDictEqual(defaults, self.fc.Preferences.preferences_defaults)
|
||||
|
||||
def test_in_memory_defaults(self):
|
||||
"""Preferences are loaded from memory"""
|
||||
defaults = self.given_defaults()
|
||||
prefs = self.fc.Preferences(defaults)
|
||||
self.assertDictEqual(defaults, prefs.preferences_defaults)
|
||||
|
||||
def test_get_good(self):
|
||||
"""Get returns results when matching an existing preference"""
|
||||
defaults = self.given_defaults()
|
||||
prefs = self.fc.Preferences(defaults)
|
||||
self.assertEqual(prefs.get("TestBool"), defaults["TestBool"])
|
||||
self.assertEqual(prefs.get("TestInt"), defaults["TestInt"])
|
||||
self.assertEqual(prefs.get("TestFloat"), defaults["TestFloat"])
|
||||
self.assertEqual(prefs.get("TestString"), defaults["TestString"])
|
||||
|
||||
def test_get_nonexistent(self):
|
||||
"""Get raises an exception when asked for a non-existent preference"""
|
||||
defaults = self.given_defaults()
|
||||
prefs = self.fc.Preferences(defaults)
|
||||
with self.assertRaises(RuntimeError):
|
||||
prefs.get("No_such_thing")
|
||||
|
||||
def test_get_bad_type(self):
|
||||
"""Get raises an exception when getting an unsupported type"""
|
||||
defaults = self.given_defaults()
|
||||
defaults["TestArray"] = ["This", "Is", "Legal", "JSON"]
|
||||
prefs = self.fc.Preferences(defaults)
|
||||
with self.assertRaises(RuntimeError):
|
||||
prefs.get("TestArray")
|
||||
|
||||
def test_set_good(self):
|
||||
"""Set works when matching an existing preference"""
|
||||
defaults = self.given_defaults()
|
||||
prefs = self.fc.Preferences(defaults)
|
||||
prefs.set("TestBool", False)
|
||||
self.assertEqual(prefs.get("TestBool"), False)
|
||||
prefs.set("TestInt", 4321)
|
||||
self.assertEqual(prefs.get("TestInt"), 4321)
|
||||
prefs.set("TestFloat", 3.14159)
|
||||
self.assertEqual(prefs.get("TestFloat"), 3.14159)
|
||||
prefs.set("TestString", "Forty two")
|
||||
self.assertEqual(prefs.get("TestString"), "Forty two")
|
||||
|
||||
def test_set_nonexistent(self):
|
||||
"""Set raises an exception when asked for a non-existent preference"""
|
||||
defaults = self.given_defaults()
|
||||
prefs = self.fc.Preferences(defaults)
|
||||
with self.assertRaises(RuntimeError):
|
||||
prefs.get("No_such_thing")
|
||||
|
||||
def test_set_bad_type(self):
|
||||
"""Set raises an exception when setting an unsupported type"""
|
||||
defaults = self.given_defaults()
|
||||
defaults["TestArray"] = ["This", "Is", "Legal", "JSON"]
|
||||
prefs = self.fc.Preferences(defaults)
|
||||
with self.assertRaises(RuntimeError):
|
||||
prefs.get("TestArray")
|
||||
|
||||
@staticmethod
|
||||
def given_defaults():
|
||||
"""Get a dictionary of fake defaults for testing"""
|
||||
defaults = {
|
||||
"TestBool": True,
|
||||
"TestInt": 42,
|
||||
"TestFloat": 1.2,
|
||||
"TestString": "Test",
|
||||
}
|
||||
return defaults
|
||||
@@ -19,6 +19,7 @@ SET(AddonManager_SRCS
|
||||
addonmanager_devmode_predictor.py
|
||||
addonmanager_devmode_validators.py
|
||||
addonmanager_firstrun.py
|
||||
addonmanager_freecad_interface.py
|
||||
addonmanager_git.py
|
||||
addonmanager_installer.py
|
||||
addonmanager_installer_gui.py
|
||||
@@ -83,6 +84,7 @@ SET(AddonManagerTestsApp_SRCS
|
||||
AddonManagerTest/app/mocks.py
|
||||
AddonManagerTest/app/test_addon.py
|
||||
AddonManagerTest/app/test_dependency_installer.py
|
||||
AddonManagerTest/app/test_freecad_interface.py
|
||||
AddonManagerTest/app/test_git.py
|
||||
AddonManagerTest/app/test_installer.py
|
||||
AddonManagerTest/app/test_macro.py
|
||||
|
||||
@@ -44,14 +44,37 @@ from AddonManagerTest.app.test_uninstaller import (
|
||||
TestAddonUninstaller as AddonManagerTestAddonUninstaller,
|
||||
TestMacroUninstaller as AddonManagerTestMacroUninstaller,
|
||||
)
|
||||
from AddonManagerTest.app.test_freecad_interface import (
|
||||
TestConsole as AddonManagerTestConsole,
|
||||
TestParameters as AddonManagerTestParameters,
|
||||
TestDataPaths as AddonManagerTestDataPaths,
|
||||
)
|
||||
|
||||
# dummy usage to get flake8 and lgtm quiet
|
||||
False if AddonManagerTestUtilities.__name__ else True
|
||||
False if AddonManagerTestAddon.__name__ else True
|
||||
False if AddonManagerTestMacro.__name__ else True
|
||||
False if AddonManagerTestGit.__name__ else True
|
||||
False if AddonManagerTestAddonInstaller.__name__ else True
|
||||
False if AddonManagerTestMacroInstaller.__name__ else True
|
||||
False if AddonManagerTestDependencyInstaller.__name__ else True
|
||||
False if AddonManagerTestAddonUninstaller.__name__ else True
|
||||
False if AddonManagerTestMacroUninstaller.__name__ else True
|
||||
|
||||
class TestListTerminator:
|
||||
pass
|
||||
|
||||
|
||||
# Basic usage mostly to get static analyzers to stop complaining about unused imports
|
||||
try:
|
||||
import FreeCAD
|
||||
except ImportError:
|
||||
FreeCAD = None
|
||||
loaded_gui_tests = [
|
||||
AddonManagerTestUtilities,
|
||||
AddonManagerTestAddon,
|
||||
AddonManagerTestMacro,
|
||||
AddonManagerTestGit,
|
||||
AddonManagerTestAddonInstaller,
|
||||
AddonManagerTestMacroInstaller,
|
||||
AddonManagerTestDependencyInstaller,
|
||||
AddonManagerTestAddonUninstaller,
|
||||
AddonManagerTestMacroUninstaller,
|
||||
AddonManagerTestConsole,
|
||||
AddonManagerTestParameters,
|
||||
AddonManagerTestDataPaths,
|
||||
TestListTerminator # Needed to prevent the last test from running twice
|
||||
]
|
||||
if FreeCAD:
|
||||
for test in loaded_gui_tests:
|
||||
FreeCAD.Console.PrintLog(f"Loaded tests from {test.__name__}\n")
|
||||
|
||||
262
src/Mod/AddonManager/addonmanager_freecad_interface.py
Normal file
262
src/Mod/AddonManager/addonmanager_freecad_interface.py
Normal file
@@ -0,0 +1,262 @@
|
||||
# ***************************************************************************
|
||||
# * *
|
||||
# * Copyright (c) 2023 FreeCAD Project Association *
|
||||
# * *
|
||||
# * This file is part of FreeCAD. *
|
||||
# * *
|
||||
# * FreeCAD is free software: you can redistribute it and/or modify it *
|
||||
# * under the terms of the GNU Lesser General Public License as *
|
||||
# * published by the Free Software Foundation, either version 2.1 of the *
|
||||
# * License, or (at your option) any later version. *
|
||||
# * *
|
||||
# * FreeCAD is distributed in the hope that it will be useful, but *
|
||||
# * WITHOUT ANY WARRANTY; without even the implied warranty of *
|
||||
# * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU *
|
||||
# * Lesser General Public License for more details. *
|
||||
# * *
|
||||
# * You should have received a copy of the GNU Lesser General Public *
|
||||
# * License along with FreeCAD. If not, see *
|
||||
# * <https://www.gnu.org/licenses/>. *
|
||||
# * *
|
||||
# ***************************************************************************
|
||||
|
||||
"""Classes to encapsulate the Addon Manager's interaction with FreeCAD, and to provide
|
||||
replacements when the Addon Manager is not run from within FreeCAD (e.g. during unit
|
||||
testing).
|
||||
|
||||
Usage:
|
||||
from addonmanager_freecad_interface import Console, DataPaths, Preferences
|
||||
"""
|
||||
|
||||
import json
|
||||
import logging
|
||||
import os
|
||||
import tempfile
|
||||
|
||||
# pylint: disable=too-few-public-methods
|
||||
|
||||
try:
|
||||
import FreeCAD
|
||||
|
||||
Console = FreeCAD.Console
|
||||
ParamGet = FreeCAD.ParamGet
|
||||
getUserAppDataDir = FreeCAD.getUserAppDataDir
|
||||
getUserMacroDir = FreeCAD.getUserMacroDir
|
||||
getUserCachePath = FreeCAD.getUserCachePath
|
||||
translate = FreeCAD.Qt.translate
|
||||
|
||||
except ImportError:
|
||||
FreeCAD = None
|
||||
getUserAppDataDir = None
|
||||
getUserCachePath = None
|
||||
getUserMacroDir = None
|
||||
|
||||
def translate(_context: str, string: str, _desc: str = "") -> str:
|
||||
return string
|
||||
|
||||
class ConsoleReplacement:
|
||||
"""If FreeCAD's Console is not available, create a replacement by redirecting FreeCAD
|
||||
log calls to Python's built-in logging facility."""
|
||||
|
||||
@staticmethod
|
||||
def PrintLog(arg: str) -> None:
|
||||
logging.log(logging.DEBUG, arg)
|
||||
|
||||
@staticmethod
|
||||
def PrintMessage(arg: str) -> None:
|
||||
logging.info(arg)
|
||||
|
||||
@staticmethod
|
||||
def PrintWarning(arg: str) -> None:
|
||||
logging.warning(arg)
|
||||
|
||||
@staticmethod
|
||||
def PrintError(arg: str) -> None:
|
||||
logging.error(arg)
|
||||
|
||||
Console = ConsoleReplacement()
|
||||
|
||||
class ParametersReplacement:
|
||||
"""Proxy for FreeCAD's Parameters when not running within FreeCAD. NOT
|
||||
serialized, only exists for the duration of the program's execution. Only
|
||||
provides the functions used by the Addon Manager, this class is not intended
|
||||
to be a complete replacement for FreeCAD's preferences system."""
|
||||
|
||||
parameters = {}
|
||||
|
||||
def GetBool(self, name: str, default: bool) -> bool:
|
||||
return self._Get(name, default)
|
||||
|
||||
def GetInt(self, name: str, default: int) -> int:
|
||||
return self._Get(name, default)
|
||||
|
||||
def GetFloat(self, name: str, default: float) -> float:
|
||||
return self._Get(name, default)
|
||||
|
||||
def GetString(self, name: str, default: str) -> str:
|
||||
return self._Get(name, default)
|
||||
|
||||
def _Get(self, name, default):
|
||||
return self.parameters[name] if name in self.parameters else default
|
||||
|
||||
def SetBool(self, name: str, value: bool) -> None:
|
||||
self.parameters[name] = value
|
||||
|
||||
def SetInt(self, name: str, value: int) -> None:
|
||||
self.parameters[name] = value
|
||||
|
||||
def SetFloat(self, name: str, value: float) -> None:
|
||||
self.parameters[name] = value
|
||||
|
||||
def SetString(self, name: str, value: str) -> None:
|
||||
self.parameters[name] = value
|
||||
|
||||
def RemBool(self, name: str) -> None:
|
||||
self.parameters.pop(name)
|
||||
|
||||
def RemInt(self, name: str) -> None:
|
||||
self.parameters.pop(name)
|
||||
|
||||
def RemFloat(self, name: str) -> None:
|
||||
self.parameters.pop(name)
|
||||
|
||||
def RemString(self, name: str) -> None:
|
||||
self.parameters.pop(name)
|
||||
|
||||
def ParamGet(_: str):
|
||||
return ParametersReplacement()
|
||||
|
||||
|
||||
class DataPaths:
|
||||
"""Provide access to various data storage paths. If not running within FreeCAD,
|
||||
all paths are temp directories. If not run within FreeCAD, all directories are
|
||||
deleted when the last reference to this class is deleted."""
|
||||
|
||||
mod_dir = None
|
||||
macro_dir = None
|
||||
cache_dir = None
|
||||
|
||||
reference_count = 0
|
||||
|
||||
def __init__(self):
|
||||
if FreeCAD:
|
||||
if self.mod_dir is None:
|
||||
self.mod_dir = os.path.join(getUserAppDataDir(), "Mod")
|
||||
if self.cache_dir is None:
|
||||
self.cache_dir = getUserCachePath()
|
||||
if self.macro_dir is None:
|
||||
self.macro_dir = getUserMacroDir(True)
|
||||
else:
|
||||
self.reference_count += 1
|
||||
if self.mod_dir is None:
|
||||
self.mod_dir = tempfile.mkdtemp()
|
||||
if self.cache_dir is None:
|
||||
self.cache_dir = tempfile.mkdtemp()
|
||||
if self.macro_dir is None:
|
||||
self.macro_dir = tempfile.mkdtemp()
|
||||
|
||||
def __del__(self):
|
||||
self.reference_count -= 1
|
||||
if not FreeCAD and self.reference_count <= 0:
|
||||
os.rmdir(self.mod_dir)
|
||||
os.rmdir(self.cache_dir)
|
||||
os.rmdir(self.macro_dir)
|
||||
self.mod_dir = None
|
||||
self.cache_dir = None
|
||||
self.macro_dir = None
|
||||
|
||||
|
||||
class Preferences:
|
||||
"""Wrap access to all user preferences. If run within FreeCAD, user preferences are
|
||||
persistent, otherwise they only exist per-run. All preferences are controlled by a
|
||||
central JSON file defining their defaults."""
|
||||
|
||||
preferences_defaults = {}
|
||||
|
||||
def __init__(self, defaults_data=None):
|
||||
"""Set up the preferences, initializing the class statics if necessary. If
|
||||
defaults_data is provided it is used as the preferences defaults. If it is not
|
||||
provided, then the defaults are read in from the standard defaults file,
|
||||
addonmanager_preferences_defaults.json, located in the same directory as this
|
||||
Python file."""
|
||||
if not self.preferences_defaults:
|
||||
if defaults_data:
|
||||
self.preferences_defaults = defaults_data
|
||||
else:
|
||||
self._load_preferences_defaults()
|
||||
self.prefs = ParamGet("User parameter:BaseApp/Preferences/Addons")
|
||||
|
||||
def get(self, name: str):
|
||||
"""Get the preference value for the given key"""
|
||||
if name not in self.preferences_defaults:
|
||||
raise RuntimeError(
|
||||
f"Unrecognized preference {name} -- did you add "
|
||||
+ "it to addonmanager_preferences_defaults.json?"
|
||||
)
|
||||
if isinstance(self.preferences_defaults[name], bool):
|
||||
return self.prefs.GetBool(name, self.preferences_defaults[name])
|
||||
if isinstance(self.preferences_defaults[name], int):
|
||||
return self.prefs.GetInt(name, self.preferences_defaults[name])
|
||||
if isinstance(self.preferences_defaults[name], float):
|
||||
return self.prefs.GetFloat(name, self.preferences_defaults[name])
|
||||
if isinstance(self.preferences_defaults[name], str):
|
||||
return self.prefs.GetString(name, self.preferences_defaults[name])
|
||||
# We don't directly support any other types from the JSON file (e.g. arrays)
|
||||
type_name = type(self.preferences_defaults[name])
|
||||
raise RuntimeError(f"Unrecognized type for {name}: {type_name}")
|
||||
|
||||
def set(self, name: str, value):
|
||||
"""Set the preference value for the given key. Must exist (e.g. must be in the
|
||||
addonmanager_preferences_defaults.json file)."""
|
||||
if name not in self.preferences_defaults:
|
||||
raise RuntimeError(
|
||||
f"Unrecognized preference {name} -- did you add "
|
||||
+ "it to addonmanager_preferences_defaults.json?"
|
||||
)
|
||||
if isinstance(self.preferences_defaults[name], bool):
|
||||
self.prefs.SetBool(name, value)
|
||||
elif isinstance(self.preferences_defaults[name], int):
|
||||
self.prefs.SetInt(name, value)
|
||||
elif isinstance(self.preferences_defaults[name], float):
|
||||
self.prefs.SetFloat(name, value)
|
||||
elif isinstance(self.preferences_defaults[name], str):
|
||||
self.prefs.SetString(name, value)
|
||||
else:
|
||||
# We don't directly support any other types from the JSON file (e.g. arrays)
|
||||
type_name = type(self.preferences_defaults[name])
|
||||
raise RuntimeError(f"Unrecognized type for {name}: {type_name}")
|
||||
|
||||
def rem(self, name: str):
|
||||
"""Remove the preference. Must have an entry in the
|
||||
addonmanager_preferences_defaults.json file."""
|
||||
if name not in self.preferences_defaults:
|
||||
raise RuntimeError(
|
||||
f"Unrecognized preference {name} -- did you add "
|
||||
+ "it to addonmanager_preferences_defaults.json?"
|
||||
)
|
||||
if isinstance(self.preferences_defaults[name], bool):
|
||||
return self.prefs.RemBool(name)
|
||||
if isinstance(self.preferences_defaults[name], int):
|
||||
return self.prefs.RemInt(name)
|
||||
if isinstance(self.preferences_defaults[name], float):
|
||||
return self.prefs.RemFloat(name)
|
||||
if isinstance(self.preferences_defaults[name], str):
|
||||
return self.prefs.RemString(name)
|
||||
# We don't directly support any other types from the JSON file (e.g. arrays)
|
||||
type_name = type(self.preferences_defaults[name])
|
||||
raise RuntimeError(f"Unrecognized type for {name}: {type_name}")
|
||||
|
||||
@classmethod
|
||||
def _load_preferences_defaults(cls, filename=None):
|
||||
"""Loads the preferences defaults JSON file from either a specified file, or
|
||||
from the standard addonmanager_preferences_defaults.json file."""
|
||||
|
||||
if filename is None:
|
||||
json_file = os.path.join(
|
||||
os.path.dirname(__file__), "addonmanager_preferences_defaults.json"
|
||||
)
|
||||
else:
|
||||
json_file = filename
|
||||
with open(json_file, "r", encoding="utf-8") as f:
|
||||
file_contents = f.read()
|
||||
cls.preferences_defaults = json.loads(file_contents)
|
||||
43
src/Mod/AddonManager/addonmanager_preferences_defaults.json
Normal file
43
src/Mod/AddonManager/addonmanager_preferences_defaults.json
Normal file
@@ -0,0 +1,43 @@
|
||||
{
|
||||
"AddonFlagsURL": "https://raw.githubusercontent.com/FreeCAD/FreeCAD-addons/master/addonflags.json",
|
||||
"AddonsRemoteCacheURL": "https://addons.freecad.org/metadata.zip",
|
||||
"AddonsUpdateStatsURL": "https://addons.freecad.org/addon_update_stats.json",
|
||||
"AutoCheck": false,
|
||||
"BlockedMacros": "BOLTS,WorkFeatures,how to install,documentation,PartsLibrary,FCGear",
|
||||
"CustomRepoHash": "",
|
||||
"CustomRepositories": "",
|
||||
"CustomToolbarName": "Auto-Created Macro Toolbar",
|
||||
"DaysBetweenUpdates": -1,
|
||||
"DownloadMacros": false,
|
||||
"GitExecutable": "Not set",
|
||||
"HideNewerFreeCADRequired": true,
|
||||
"HideObsolete": true,
|
||||
"HidePy2": true,
|
||||
"KnownPythonVersions": "[]",
|
||||
"LastCacheUpdate": "never",
|
||||
"MacroCacheUpdateFrequency": 7,
|
||||
"MacroGitURL": "https://github.com/FreeCAD/FreeCAD-Macros",
|
||||
"MacroUpdateStatsURL": "https://addons.freecad.org/macro_update_stats.json",
|
||||
"MacroWikiURL": "https://wiki.freecad.org/Macros_recipes",
|
||||
"NoProxyCheck": "",
|
||||
"PackageTypeSelection": 1,
|
||||
"PrimaryAddonsSubmoduleURL": "https://raw.githubusercontent.com/FreeCAD/FreeCAD-addons/master/.gitmodules",
|
||||
"ProxyUrl": "",
|
||||
"PythonExecutableForPip": "Not set",
|
||||
"RemoteIconCacheURL": "https://addons.freecad.org/icon_cache.zip",
|
||||
"SelectedAddon": "",
|
||||
"ShowBranchSwitcher": false,
|
||||
"StatusSelection": 0,
|
||||
"SystemProxyCheck": "",
|
||||
"UpdateFrequencyComboEntry": 0,
|
||||
"UserProxyCheck": "",
|
||||
"ViewStyle": 1,
|
||||
"WindowHeight": 600,
|
||||
"WindowWidth": 800,
|
||||
"alwaysAskForToolbar": true,
|
||||
"devModeLastSelectedLicense": "LGPLv2.1",
|
||||
"developerMode": false,
|
||||
"disableGit": false,
|
||||
"dontShowAddMacroButtonDialog": false,
|
||||
"readWarning2022": false
|
||||
}
|
||||
Reference in New Issue
Block a user