Skip to content

Commit

Permalink
Resolver to resolve, AiiDAConfigDir is singleton
Browse files Browse the repository at this point in the history
  • Loading branch information
unkcpz committed Nov 22, 2024
1 parent bdfedca commit a9980a2
Show file tree
Hide file tree
Showing 12 changed files with 64 additions and 58 deletions.
4 changes: 2 additions & 2 deletions src/aiida/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@

from aiida.common.log import configure_logging # noqa: F401
from aiida.manage.configuration import get_config_option, get_profile, load_profile, profile_context # noqa: F401
from aiida.manage.configuration.settings import AiiDAConfigPathResolver
from aiida.manage.configuration.settings import AiiDAConfigDir

__copyright__ = (
'Copyright (c), This file is part of the AiiDA platform. '
Expand All @@ -37,7 +37,7 @@
__paper_short__ = 'S. P. Huber et al., Scientific Data 7, 300 (2020).'

# Initialize the configuration directory settings
AiiDAConfigPathResolver.set_configuration_directory()
AiiDAConfigDir.set_configuration_directory()


def get_strict_version():
Expand Down
4 changes: 2 additions & 2 deletions src/aiida/cmdline/commands/cmd_presto.py
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ def detect_postgres_config(
"""
import secrets

from aiida.manage.configuration.settings import AiiDAConfigPathResolver
from aiida.manage.configuration.settings import AiiDAConfigDir
from aiida.manage.external.postgres import Postgres

dbinfo = {
Expand All @@ -92,7 +92,7 @@ def detect_postgres_config(
except Exception as exception:
raise ConnectionError(f'Unable to automatically create the PostgreSQL user and database: {exception}')

aiida_config_folder = AiiDAConfigPathResolver.get_configuration_directory()
aiida_config_folder = AiiDAConfigDir.get_configuration_directory()

return {
'database_hostname': postgres_hostname,
Expand Down
4 changes: 2 additions & 2 deletions src/aiida/cmdline/commands/cmd_profile.py
Original file line number Diff line number Diff line change
Expand Up @@ -169,9 +169,9 @@ def profile_list():
# This can happen for a fresh install and the `verdi setup` has not yet been run. In this case it is still nice
# to be able to see the configuration directory, for instance for those who have set `AIIDA_PATH`. This way
# they can at least verify that it is correctly set.
from aiida.manage.configuration.settings import AiiDAConfigPathResolver
from aiida.manage.configuration.settings import AiiDAConfigDir

echo.echo_report(f'configuration folder: {AiiDAConfigPathResolver.get_configuration_directory()}')
echo.echo_report(f'configuration folder: {AiiDAConfigDir.get_configuration_directory()}')
echo.echo_critical(str(exception))
else:
echo.echo_report(f'configuration folder: {config.dirpath}')
Expand Down
4 changes: 2 additions & 2 deletions src/aiida/cmdline/commands/cmd_status.py
Original file line number Diff line number Diff line change
Expand Up @@ -61,11 +61,11 @@ def verdi_status(print_traceback, no_rmq):
from aiida.common.docs import URL_NO_BROKER
from aiida.common.exceptions import ConfigurationError
from aiida.engine.daemon.client import DaemonException, DaemonNotRunningException
from aiida.manage.configuration.settings import AiiDAConfigPathResolver
from aiida.manage.configuration.settings import AiiDAConfigDir
from aiida.manage.manager import get_manager

exit_code = ExitCode.SUCCESS
configure_directory = AiiDAConfigPathResolver.get_configuration_directory()
configure_directory = AiiDAConfigDir.get_configuration_directory()

print_status(ServiceStatus.UP, 'version', f'AiiDA v{__version__}')
print_status(ServiceStatus.UP, 'config', configure_directory)
Expand Down
4 changes: 2 additions & 2 deletions src/aiida/cmdline/params/options/commands/setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -66,10 +66,10 @@ def get_repository_uri_default(ctx):
"""
import os

from aiida.manage.configuration.settings import AiiDAConfigPathResolver
from aiida.manage.configuration.settings import AiiDAConfigDir

validate_profile_parameter(ctx)
configure_directory = AiiDAConfigPathResolver.get_configuration_directory()
configure_directory = AiiDAConfigDir.get_configuration_directory()

return os.path.join(configure_directory, 'repository', ctx.params['profile'].name)

Expand Down
4 changes: 2 additions & 2 deletions src/aiida/manage/configuration/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@

from __future__ import annotations

from aiida.manage.configuration.settings import AiiDAConfigPathResolver
from aiida.manage.configuration.settings import AiiDAConfigDir

# AUTO-GENERATED
# fmt: off
Expand Down Expand Up @@ -70,7 +70,7 @@ def get_config_path():
"""Returns path to aiida configuration file."""
from .settings import DEFAULT_CONFIG_FILE_NAME

return os.path.join(AiiDAConfigPathResolver.get_configuration_directory(), DEFAULT_CONFIG_FILE_NAME)
return os.path.join(AiiDAConfigDir.get_configuration_directory(), DEFAULT_CONFIG_FILE_NAME)


def load_config(create=False) -> 'Config':
Expand Down
52 changes: 29 additions & 23 deletions src/aiida/manage/configuration/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,19 +26,42 @@
DEFAULT_DAEMON_LOG_DIR_NAME = 'log'
DEFAULT_ACCESS_CONTROL_DIR_NAME = 'access'

__all__ = ('AiiDAConfigPathResolver',)
__all__ = ('AiiDAConfigPathResolver', 'AiiDAConfigDir')


@final
class AiiDAConfigPathResolver:
"""Path resolver for setting and getting the path to configuration directory, daemon dir,
daemon log dir and access control dir. The locations are all trivially derived from the config location,
The class provide dedicated setter/getter for configuration_directory."""
class AiiDAConfigDir:
"""Singleton for setting and getting the path to configuration directory."""

_glb_aiida_config_folder: pathlib.Path = pathlib.Path(DEFAULT_AIIDA_PATH).expanduser() / DEFAULT_CONFIG_DIR_NAME

@classmethod
def get_configuration_directory(cls):
"""Return the path of the configuration directory."""
return cls._glb_aiida_config_folder

@classmethod
def set_configuration_directory(cls, aiida_config_folder: pathlib.Path | None = None) -> None:
"""Set the configuration directory, related global variables and create instance directories.
The location of the configuration directory is defined by ``aiida_config_folder`` or if not defined,
the path that is returned by ``get_configuration_directory_from_envvar``. If the directory does not exist yet,
it is created, together with all its subdirectories.
"""
cls._glb_aiida_config_folder = aiida_config_folder or _get_configuration_directory_from_envvar()

_create_instance_directories(cls._glb_aiida_config_folder)


@final
class AiiDAConfigPathResolver:
"""For resolving configuration directory, daemon dir,
daemon log dir and access control dir.
The locations are all trivially derived from the config location,
"""

def __init__(self, config_folder: pathlib.Path | None) -> None:
self._aiida_path = config_folder or self._glb_aiida_config_folder
self._aiida_path = config_folder or AiiDAConfigDir.get_configuration_directory()

@property
def aiida_path(self) -> pathlib.Path:
Expand All @@ -56,23 +79,6 @@ def daemon_log_dir(self) -> pathlib.Path:
def access_control_dir(self) -> pathlib.Path:
return self._aiida_path / DEFAULT_ACCESS_CONTROL_DIR_NAME

@classmethod
def get_configuration_directory(cls):
"""Return the path of the configuration directory."""
return cls._glb_aiida_config_folder

@classmethod
def set_configuration_directory(cls, aiida_config_folder: pathlib.Path | None = None) -> None:
"""Set the configuration directory, related global variables and create instance directories.
The location of the configuration directory is defined by ``aiida_config_folder`` or if not defined,
the path that is returned by ``get_configuration_directory_from_envvar``. If the directory does not exist yet,
it is created, together with all its subdirectories.
"""
cls._glb_aiida_config_folder = aiida_config_folder or _get_configuration_directory_from_envvar()

_create_instance_directories(cls._glb_aiida_config_folder)


def _create_instance_directories(aiida_config_folder: pathlib.Path | None) -> None:
"""Create the base directories required for a new AiiDA instance.
Expand Down
4 changes: 2 additions & 2 deletions src/aiida/storage/sqlite_dos/backend.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@
from aiida.common import exceptions
from aiida.common.log import AIIDA_LOGGER
from aiida.manage.configuration.profile import Profile
from aiida.manage.configuration.settings import AiiDAConfigPathResolver
from aiida.manage.configuration.settings import AiiDAConfigDir
from aiida.orm.implementation import BackendEntity
from aiida.storage.log import MIGRATE_LOGGER
from aiida.storage.psql_dos.models.settings import DbSetting
Expand Down Expand Up @@ -204,7 +204,7 @@ class Model(BaseModel, defer_build=True):
title='Directory of the backend',
description='Filepath of the directory in which to store data for this backend.',
default_factory=lambda: str(
AiiDAConfigPathResolver.get_configuration_directory() / 'repository' / f'sqlite_dos_{uuid4().hex}'
AiiDAConfigDir.get_configuration_directory() / 'repository' / f'sqlite_dos_{uuid4().hex}'
),
)

Expand Down
6 changes: 3 additions & 3 deletions src/aiida/tools/pytest_fixtures/configuration.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@

import pytest

from aiida.manage.configuration.settings import AiiDAConfigPathResolver
from aiida.manage.configuration.settings import AiiDAConfigDir

if t.TYPE_CHECKING:
from aiida.manage.configuration.config import Config
Expand Down Expand Up @@ -55,15 +55,15 @@ def factory(dirpath: pathlib.Path):

dirpath_config = dirpath / settings.DEFAULT_CONFIG_DIR_NAME
os.environ[settings.DEFAULT_AIIDA_PATH_VARIABLE] = str(dirpath_config)
AiiDAConfigPathResolver.set_configuration_directory(dirpath_config)
AiiDAConfigDir.set_configuration_directory(dirpath_config)
config = get_config(create=True)

try:
yield config
finally:
if current_config:
reset_config()
AiiDAConfigPathResolver.set_configuration_directory(pathlib.Path(current_config.dirpath))
AiiDAConfigDir.set_configuration_directory(pathlib.Path(current_config.dirpath))
get_config()

if current_path_variable is None:
Expand Down
6 changes: 3 additions & 3 deletions tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@
from aiida.common.folders import Folder
from aiida.common.links import LinkType
from aiida.manage.configuration import Profile, get_config, load_profile
from aiida.manage.configuration.settings import AiiDAConfigPathResolver
from aiida.manage.configuration.settings import AiiDAConfigDir

if t.TYPE_CHECKING:
from aiida.manage.configuration.config import Config
Expand Down Expand Up @@ -343,7 +343,7 @@ def empty_config(tmp_path) -> Config:

# Set the configuration directory to a temporary directory. This will create the necessary folders for an empty
# AiiDA configuration and set relevant global variables in :mod:`aiida.manage.configuration.settings`.
AiiDAConfigPathResolver.set_configuration_directory(tmp_path)
AiiDAConfigDir.set_configuration_directory(tmp_path)

# The constructor of `Config` called by `load_config` will print warning messages about migrating it
with Capturing():
Expand All @@ -361,7 +361,7 @@ def empty_config(tmp_path) -> Config:
# like the :class:`aiida.engine.daemon.client.DaemonClient` will not function properly after a test that uses
# this fixture because the paths of the daemon files would still point to the path of the temporary config
# folder created by this fixture.
AiiDAConfigPathResolver.set_configuration_directory(pathlib.Path(current_config_path))
AiiDAConfigDir.set_configuration_directory(pathlib.Path(current_config_path))

# Reload the original profile
manager.load_profile(current_profile_name)
Expand Down
24 changes: 12 additions & 12 deletions tests/manage/configuration/test_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
from aiida.manage.configuration.config import Config
from aiida.manage.configuration.migrations import CURRENT_CONFIG_VERSION, OLDEST_COMPATIBLE_CONFIG_VERSION
from aiida.manage.configuration.options import get_option
from aiida.manage.configuration.settings import AiiDAConfigPathResolver
from aiida.manage.configuration.settings import AiiDAConfigDir
from aiida.storage.sqlite_temp import SqliteTempBackend


Expand All @@ -43,7 +43,7 @@ def cache_aiida_path_variable():

# Make sure to reset the global variables set by the following call that are dependent on the environment variable
# ``DEFAULT_AIIDA_PATH_VARIABLE``. It may have been changed by a test using this fixture.
AiiDAConfigPathResolver.set_configuration_directory()
AiiDAConfigDir.set_configuration_directory()


@pytest.mark.filterwarnings('ignore:Creating AiiDA configuration folder')
Expand All @@ -66,11 +66,11 @@ def test_environment_variable_not_set(chdir_tmp_path, monkeypatch):
del os.environ[settings.DEFAULT_AIIDA_PATH_VARIABLE]
except KeyError:
pass
AiiDAConfigPathResolver.set_configuration_directory()
AiiDAConfigDir.set_configuration_directory()

config_folder = chdir_tmp_path / settings.DEFAULT_CONFIG_DIR_NAME
assert os.path.isdir(config_folder)
assert AiiDAConfigPathResolver.get_configuration_directory() == pathlib.Path(config_folder)
assert AiiDAConfigDir.get_configuration_directory() == pathlib.Path(config_folder)


@pytest.mark.filterwarnings('ignore:Creating AiiDA configuration folder')
Expand All @@ -79,12 +79,12 @@ def test_environment_variable_set_single_path_without_config_folder(tmp_path):
"""If `AIIDA_PATH` is set but does not contain a configuration folder, it should be created."""
# Set the environment variable and call configuration initialization
os.environ[settings.DEFAULT_AIIDA_PATH_VARIABLE] = str(tmp_path)
AiiDAConfigPathResolver.set_configuration_directory()
AiiDAConfigDir.set_configuration_directory()

# This should have created the configuration directory in the path
config_folder = tmp_path / settings.DEFAULT_CONFIG_DIR_NAME
assert config_folder.is_dir()
assert AiiDAConfigPathResolver.get_configuration_directory() == config_folder
assert AiiDAConfigDir.get_configuration_directory() == config_folder


@pytest.mark.filterwarnings('ignore:Creating AiiDA configuration folder')
Expand All @@ -95,12 +95,12 @@ def test_environment_variable_set_single_path_with_config_folder(tmp_path):

# Set the environment variable and call configuration initialization
os.environ[settings.DEFAULT_AIIDA_PATH_VARIABLE] = str(tmp_path)
AiiDAConfigPathResolver.set_configuration_directory()
AiiDAConfigDir.set_configuration_directory()

# This should have created the configuration directory in the path
config_folder = tmp_path / settings.DEFAULT_CONFIG_DIR_NAME
assert config_folder.is_dir()
assert AiiDAConfigPathResolver.get_configuration_directory() == config_folder
assert AiiDAConfigDir.get_configuration_directory() == config_folder


@pytest.mark.filterwarnings('ignore:Creating AiiDA configuration folder')
Expand All @@ -115,12 +115,12 @@ def test_environment_variable_path_including_config_folder(tmp_path):
"""
# Set the environment variable with a path that include base folder name and call config initialization
os.environ[settings.DEFAULT_AIIDA_PATH_VARIABLE] = str(tmp_path / settings.DEFAULT_CONFIG_DIR_NAME)
AiiDAConfigPathResolver.set_configuration_directory()
AiiDAConfigDir.set_configuration_directory()

# This should have created the configuration directory in the pathpath
config_folder = tmp_path / settings.DEFAULT_CONFIG_DIR_NAME
assert config_folder.is_dir()
assert AiiDAConfigPathResolver.get_configuration_directory() == config_folder
assert AiiDAConfigDir.get_configuration_directory() == config_folder


@pytest.mark.filterwarnings('ignore:Creating AiiDA configuration folder')
Expand All @@ -138,12 +138,12 @@ def test_environment_variable_set_multiple_path(tmp_path):
# Set the environment variable to contain three paths and call configuration initialization
env_variable = f'{directory_a}:{directory_b}:{directory_c}'
os.environ[settings.DEFAULT_AIIDA_PATH_VARIABLE] = env_variable
AiiDAConfigPathResolver.set_configuration_directory()
AiiDAConfigDir.set_configuration_directory()

# This should have created the configuration directory in the last path
config_folder = directory_c / settings.DEFAULT_CONFIG_DIR_NAME
assert os.path.isdir(config_folder)
assert AiiDAConfigPathResolver.get_configuration_directory() == config_folder
assert AiiDAConfigDir.get_configuration_directory() == config_folder


def compare_config_in_memory_and_on_disk(config, filepath):
Expand Down
6 changes: 3 additions & 3 deletions tests/manage/test_caching_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ def test_merge_deprecated_yaml(tmp_path):
from aiida.common.warnings import AiidaDeprecationWarning
from aiida.manage import configuration, get_manager
from aiida.manage.configuration import get_config_option, load_profile
from aiida.manage.configuration.settings import AiiDAConfigPathResolver
from aiida.manage.configuration.settings import AiiDAConfigDir

# Store the current configuration instance and config directory path
current_config = configuration.CONFIG
Expand All @@ -58,7 +58,7 @@ def test_merge_deprecated_yaml(tmp_path):
configuration.CONFIG = None

# Create a temporary folder, set it as the current config directory path
AiiDAConfigPathResolver.set_configuration_directory(pathlib.Path(tmp_path))
AiiDAConfigDir.set_configuration_directory(pathlib.Path(tmp_path))
config_dictionary = json.loads(
pathlib.Path(__file__)
.parent.joinpath('configuration/migrations/test_samples/reference/6.json')
Expand Down Expand Up @@ -87,7 +87,7 @@ def test_merge_deprecated_yaml(tmp_path):
# Reset the config folder path and the config instance. Note this will always be executed after the yield no
# matter what happened in the test that used this fixture.
get_manager().unload_profile()
AiiDAConfigPathResolver.set_configuration_directory(current_config_path)
AiiDAConfigDir.set_configuration_directory(current_config_path)
configuration.CONFIG = current_config
load_profile(current_profile_name)

Expand Down

0 comments on commit a9980a2

Please sign in to comment.