diff --git a/src/pymodaq_utils/config.py b/src/pymodaq_utils/config.py index 051b7354..6d57369d 100644 --- a/src/pymodaq_utils/config.py +++ b/src/pymodaq_utils/config.py @@ -13,6 +13,7 @@ if TYPE_CHECKING: from pyqtgraph import Parameter +from pymodaq_utils.env_utils import guess_virtual_environment, hash_pymodaq_packages_version try: USER = environ['USERNAME'] if sys.platform == 'win32' else environ['USER'] @@ -26,6 +27,8 @@ KeyType = TypeVar('KeyType') +PYMODAQ_USER_FOLDER_NAME = f'.pymodaq_{ guess_virtual_environment() }_{ hash_pymodaq_packages_version() }' + def deep_update(mapping: Dict[KeyType, Any], *updating_mappings: Dict[KeyType, Any]) -> Dict[KeyType, Any]: """ Make sure a dictionary is updated using another dict in any nested level Taken from Pydantic v1 @@ -126,11 +129,12 @@ def get_set_local_dir(user=False) -> Path: ------- Path: the local path """ + if user: - local_path = get_set_path(Path.home(), '.pymodaq') + local_dir = get_set_path(Path.home(), PYMODAQ_USER_FOLDER_NAME) else: - local_path = get_set_path(CONFIG_BASE_PATH, '.pymodaq') - return local_path + local_dir = get_set_path(CONFIG_BASE_PATH, '.pymodaq') + return local_dir def get_config_file(config_file_name: str, user=False): diff --git a/src/pymodaq_utils/env_utils.py b/src/pymodaq_utils/env_utils.py new file mode 100644 index 00000000..6ec8348c --- /dev/null +++ b/src/pymodaq_utils/env_utils.py @@ -0,0 +1,39 @@ +import os +import sys + +from importlib import metadata +from importlib.metadata import PackageNotFoundError +from pathlib import Path + +def guess_virtual_environment() -> str: + ''' + Try to guess the current python environment used. + + Returns + ------- + str: the guessed environment name or the string "unknown" + ''' + def _venv_name_or_path(): + #Try to guess from system environment + for var in ['VIRTUAL_ENV', 'CONDA_DEFAULT_ENV', 'PYENV_VERSION', 'TOX_ENV_NAME']: + value = os.environ.get(var) + if value: + return value + #if true, probably running in a venv + if sys.prefix != sys.base_prefix: + return sys.prefix + return 'unknown' + return Path(_venv_name_or_path()).name + + +def hash_pymodaq_packages_version() -> str: + import hashlib + versions = [] + for package in ['pymodaq_utils', 'pymodaq_data', 'pymodaq_gui', 'PyMoDAQ']: + try: + versions.append(metadata.version(package)) + except PackageNotFoundError: + #package not found, it can be skipped + pass + hashed = hashlib.sha256(''.join(versions).encode()) + return hashed.digest()[:8].hex() diff --git a/src/pymodaq_utils/environment.py b/src/pymodaq_utils/environment.py index 56898239..26d990ed 100644 --- a/src/pymodaq_utils/environment.py +++ b/src/pymodaq_utils/environment.py @@ -10,32 +10,14 @@ from pymodaq_utils import config as configmod from pymodaq_utils.config import get_set_local_dir from pymodaq_utils.logger import set_logger, get_module_name - +from pymodaq_utils.env_utils import guess_virtual_environment logger = set_logger(get_module_name(__file__)) config = configmod.Config() -def guess_virtual_environment() -> str: - ''' - Try to guess the current python environment used. - Returns - ------- - str: the guessed environment name or the string "unknown" - ''' - def _venv_name_or_path(): - #Try to guess from system environment - for var in ['VIRTUAL_ENV', 'CONDA_DEFAULT_ENV', 'PYENV_VERSION', 'TOX_ENV_NAME']: - value = os.environ.get(var) - if value: - return value - #if true, probably running in a venv - if sys.prefix != sys.base_prefix: - return sys.prefix - return 'unknown' - return Path(_venv_name_or_path()).name class EnvironmentBackupManager: diff --git a/tests/environment_test.py b/tests/environment_test.py index b1c9a5bc..ef5b3ad7 100644 --- a/tests/environment_test.py +++ b/tests/environment_test.py @@ -1,20 +1,21 @@ import os, sys +import importlib import pytest - -from pymodaq_utils.environment import guess_virtual_environment, PythonEnvironment +from pymodaq_utils.env_utils import guess_virtual_environment, hash_pymodaq_packages_version +from pymodaq_utils.environment import PythonEnvironment POSSIBLE_VENV_VARIABLES = ['VIRTUAL_ENV', 'CONDA_DEFAULT_ENV', 'PYENV_VERSION', 'TOX_ENV_NAME'] class TestGuessVirtualEnvironment: - @pytest.mark.parametrize("var", POSSIBLE_VENV_VARIABLES) + @pytest.mark.parametrize('var', POSSIBLE_VENV_VARIABLES) def test_from_environment_var(self, monkeypatch, var): - venv_name = "if_your_environment_is_called_like_that_you_should_probably_change_it" + venv_name = 'if_your_environment_is_called_like_that_you_should_probably_change_it' # set one of the possible environment variable to the var name value # and remove the others - monkeypatch.setenv(var, os.path.join("/home/./folder/", venv_name)) + monkeypatch.setenv(var, os.path.join('/home/./folder/', venv_name)) for other in [o for o in POSSIBLE_VENV_VARIABLES if o != var]: monkeypatch.delenv(other, raising=False) @@ -22,11 +23,11 @@ def test_from_environment_var(self, monkeypatch, var): def test_from_prefix(self, monkeypatch): - venv_name = "if_your_environment_is_called_like_that_you_should_probably_change_it" + venv_name = 'if_your_environment_is_called_like_that_you_should_probably_change_it' for var in POSSIBLE_VENV_VARIABLES: monkeypatch.delenv(var, raising=False) - monkeypatch.setattr(sys, "prefix", os.path.join("/home/./folder/", venv_name)) + monkeypatch.setattr(sys, 'prefix', os.path.join('/home/./folder/', venv_name)) assert guess_virtual_environment() == venv_name @@ -34,7 +35,7 @@ def test_unknown(self, monkeypatch): for var in POSSIBLE_VENV_VARIABLES: monkeypatch.delenv(var, raising=False) - monkeypatch.setattr(sys, "prefix", sys.base_prefix) + monkeypatch.setattr(sys, 'prefix', sys.base_prefix) assert guess_virtual_environment() == 'unknown' class TestPythonEnvironment: @@ -71,4 +72,25 @@ def test_freeze(self): e1 = PythonEnvironment.from_freeze() e2 = PythonEnvironment.from_freeze() - assert e1 == e2, "Two pip freeze in a row don't return the same environment. Ensure no pip install are running." \ No newline at end of file + assert e1 == e2, 'Two pip freeze in a row don\'t return the same environment. Ensure no pip install are running.' + +class TestHashPymodaqPackageVersion: + @pytest.mark.parametrize( + 'versions, hashed', [ + ({'pymodaq_utils': '1.0.0', 'pymodaq_data': '2.0.0', 'pymodaq_gui': '3.0.0', 'PyMoDAQ': '4.0.0'}, '94722337cbb21550'), + ({'pymodaq_utils': '9.9.9', 'pymodaq_data': '8.8.8', 'pymodaq_gui': '7.7.7', 'PyMoDAQ': '6.6.6'}, 'eb57be0fff8e14f5') + ] + ) + def test_hash_pymodaq_package_version(self, monkeypatch, versions, hashed): + def mock_version(package): + return versions[package] + + monkeypatch.setattr(importlib.metadata, 'version', mock_version) + + assert hash_pymodaq_packages_version() == hashed + + def test_hash_similar(self): + h1 = hash_pymodaq_packages_version() + h2 = hash_pymodaq_packages_version() + + assert h1 == h2, 'Two hashes of PyMoDAQ package version numbers in a row return different values. Ensure no pip install are' \ No newline at end of file