Skip to content

Commit

Permalink
strict Singleton pattern
Browse files Browse the repository at this point in the history
changes applied to not allow glb var escape from module scope
  • Loading branch information
unkcpz committed Nov 15, 2024
1 parent ad1086f commit c9a7c04
Show file tree
Hide file tree
Showing 11 changed files with 47 additions and 53 deletions.
6 changes: 4 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 glb_aiida_config_folder
from aiida.manage.configuration.settings import get_configuration_directory
from aiida.manage.external.postgres import Postgres

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

aiida_config_folder = get_configuration_directory()

return {
'database_hostname': postgres_hostname,
'database_port': postgres_port,
'database_name': database_name,
'database_username': database_username,
'database_password': database_password,
'repository_uri': f'file://{glb_aiida_config_folder / "repository" / profile_name}',
'repository_uri': f'file://{aiida_config_folder / "repository" / profile_name}',
}


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 glb_aiida_config_folder
from aiida.manage.configuration.settings import get_configuration_directory

Check warning on line 172 in src/aiida/cmdline/commands/cmd_profile.py

View check run for this annotation

Codecov / codecov/patch

src/aiida/cmdline/commands/cmd_profile.py#L172

Added line #L172 was not covered by tests

echo.echo_report(f'configuration folder: {glb_aiida_config_folder}')
echo.echo_report(f'configuration folder: {get_configuration_directory()}')

Check warning on line 174 in src/aiida/cmdline/commands/cmd_profile.py

View check run for this annotation

Codecov / codecov/patch

src/aiida/cmdline/commands/cmd_profile.py#L174

Added line #L174 was not covered by tests
echo.echo_critical(str(exception))
else:
echo.echo_report(f'configuration folder: {config.dirpath}')
Expand Down
5 changes: 3 additions & 2 deletions src/aiida/cmdline/commands/cmd_status.py
Original file line number Diff line number Diff line change
Expand Up @@ -61,13 +61,14 @@ 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 glb_aiida_config_folder
from aiida.manage.configuration.settings import get_configuration_directory
from aiida.manage.manager import get_manager

exit_code = ExitCode.SUCCESS
configure_directory = get_configuration_directory()

print_status(ServiceStatus.UP, 'version', f'AiiDA v{__version__}')
print_status(ServiceStatus.UP, 'config', glb_aiida_config_folder)
print_status(ServiceStatus.UP, 'config', str(configure_directory))

manager = get_manager()

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,11 +66,11 @@ def get_repository_uri_default(ctx):
"""
import os

from aiida.manage.configuration.settings import glb_aiida_config_folder
from aiida.manage.configuration.settings import get_configuration_directory

validate_profile_parameter(ctx)

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


def get_quicksetup_repository_uri(ctx, param, value):
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 @@ -66,9 +66,9 @@

def get_config_path():
"""Returns path to .aiida configuration directory."""
from .settings import DEFAULT_CONFIG_FILE_NAME, glb_aiida_config_folder
from .settings import DEFAULT_CONFIG_FILE_NAME, get_configuration_directory

return os.path.join(glb_aiida_config_folder, DEFAULT_CONFIG_FILE_NAME)
return os.path.join(str(get_configuration_directory()), DEFAULT_CONFIG_FILE_NAME)


def load_config(create=False) -> 'Config':
Expand Down
6 changes: 4 additions & 2 deletions src/aiida/manage/configuration/profile.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@

from aiida.common import exceptions
from aiida.common.lang import type_check
from aiida.manage.configuration.settings import AiiDAConfigPathResolver
from aiida.manage.configuration.settings import AiiDAConfigPathResolver, get_configuration_directory

from .options import parse_option

Expand Down Expand Up @@ -67,7 +67,9 @@ def __init__(

self._attributes[self.KEY_UUID] = uuid4().hex

self._config_path_resolver: AiiDAConfigPathResolver = AiiDAConfigPathResolver(config_folder)
self._config_path_resolver: AiiDAConfigPathResolver = AiiDAConfigPathResolver(
config_folder or get_configuration_directory()
)

def __repr__(self) -> str:
return f'Profile<uuid={self.uuid!r} name={self.name!r}>'
Expand Down
46 changes: 18 additions & 28 deletions src/aiida/manage/configuration/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,21 +27,15 @@
DEFAULT_ACCESS_CONTROL_DIR_NAME = 'access'

# Assign defaults which may be overriden in set_configuration_directory() below
glb_aiida_config_folder: pathlib.Path = pathlib.Path(DEFAULT_AIIDA_PATH).expanduser() / DEFAULT_CONFIG_DIR_NAME
_glb_aiida_config_folder: pathlib.Path = pathlib.Path(DEFAULT_AIIDA_PATH).expanduser() / DEFAULT_CONFIG_DIR_NAME


@final
class AiiDAConfigPathResolver:
"""Path resolver for getting daemon dir, daemon log dir and access control dir location.
"""Path resolver for getting daemon dir, daemon log dir and access control dir location."""

If ``config_folder`` is ``None``, ``~/.aiida`` will be the default root config folder.
"""

def __init__(self, config_folder: pathlib.Path | None = None) -> None:
if config_folder is None:
self._aiida_path = glb_aiida_config_folder
else:
self._aiida_path = config_folder
def __init__(self, config_folder: pathlib.Path) -> None:
self._aiida_path = config_folder

@property
def aiida_path(self) -> pathlib.Path:
Expand All @@ -63,12 +57,13 @@ def access_control_dir(self) -> pathlib.Path:
def create_instance_directories(aiida_config_folder: pathlib.Path | None) -> None:
"""Create the base directories required for a new AiiDA instance.
This will create the base AiiDA directory defined by the glb_aiida_config_folder variable, unless it already exists.
This will create the base AiiDA directory defined by the _glb_aiida_config_folder variable,
unless it already exists.
Subsequently, it will create the daemon directory within it and the daemon log directory.
"""
from aiida.common import ConfigurationError

path_resolver = AiiDAConfigPathResolver(aiida_config_folder)
path_resolver = AiiDAConfigPathResolver(aiida_config_folder or _glb_aiida_config_folder)

list_of_paths = [
path_resolver.aiida_path,
Expand Down Expand Up @@ -98,22 +93,16 @@ def get_configuration_directory():
The location of the configuration directory is defined following these heuristics in order:
* If the ``AIIDA_PATH`` variable is set, all the paths will be checked to see if they contain a
configuration folder. The first one to be encountered will be set as ``glb_aiida_config_folder``.
configuration folder. The first one to be encountered will be set as ``_glb_aiida_config_folder``.
If none of them contain one, the last path defined in the environment variable considered is used.
* If an existing directory is still not found, the ``DEFAULT_AIIDA_PATH`` is used.
:returns: The path of the configuration directory.
"""
dirpath_config = get_configuration_directory_from_envvar()

# If no existing configuration directory is found, fall back to the default
if dirpath_config is None:
dirpath_config = pathlib.Path(DEFAULT_AIIDA_PATH).expanduser() / DEFAULT_CONFIG_DIR_NAME

return dirpath_config
return _glb_aiida_config_folder


def get_configuration_directory_from_envvar() -> pathlib.Path | None:
def get_configuration_directory_from_envvar() -> pathlib.Path:
"""Return the path of a config directory from the ``AIIDA_PATH`` environment variable.
The environment variable should be a colon separated string of filepaths that either point directly to a config
Expand All @@ -124,12 +113,13 @@ def get_configuration_directory_from_envvar() -> pathlib.Path | None:
"""
environment_variable = os.environ.get(DEFAULT_AIIDA_PATH_VARIABLE)

if environment_variable is None:
return None
default_dirpath_config = pathlib.Path(DEFAULT_AIIDA_PATH).expanduser() / DEFAULT_CONFIG_DIR_NAME

dirpath_config = None
if environment_variable is None:
return default_dirpath_config

# Loop over all the paths in the ``AIIDA_PATH`` variable to see if any of them contain a configuration folder
dirpath_config = None
for base_dir_path in [path for path in environment_variable.split(':') if path]:
dirpath_config = pathlib.Path(base_dir_path).expanduser()

Expand All @@ -142,7 +132,7 @@ def get_configuration_directory_from_envvar() -> pathlib.Path | None:
if dirpath_config.is_dir():
break

return dirpath_config
return dirpath_config or default_dirpath_config


def set_configuration_directory(aiida_config_folder: pathlib.Path | None = None) -> None:
Expand All @@ -152,10 +142,10 @@ def set_configuration_directory(aiida_config_folder: pathlib.Path | None = None)
is returned by ``get_configuration_directory``. If the directory does not exist yet, it is created, together with
all its subdirectories.
"""
global glb_aiida_config_folder # noqa: PLW0603
glb_aiida_config_folder = aiida_config_folder or get_configuration_directory()
global _glb_aiida_config_folder # noqa: PLW0603
_glb_aiida_config_folder = aiida_config_folder or get_configuration_directory_from_envvar()

create_instance_directories(glb_aiida_config_folder)
create_instance_directories(_glb_aiida_config_folder)


# Initialize the configuration directory settings
Expand Down
5 changes: 2 additions & 3 deletions src/aiida/manage/tests/pytest_fixtures.py
Original file line number Diff line number Diff line change
Expand Up @@ -178,8 +178,7 @@ def aiida_instance(

dirpath_config = tmp_path_factory.mktemp('config')
os.environ[settings.DEFAULT_AIIDA_PATH_VARIABLE] = str(dirpath_config)
settings.glb_aiida_config_folder = dirpath_config
settings.set_configuration_directory()
settings.set_configuration_directory(dirpath_config)

Check warning on line 181 in src/aiida/manage/tests/pytest_fixtures.py

View check run for this annotation

Codecov / codecov/patch

src/aiida/manage/tests/pytest_fixtures.py#L181

Added line #L181 was not covered by tests
configuration.CONFIG = configuration.load_config(create=True)

try:
Expand All @@ -191,7 +190,7 @@ def aiida_instance(
else:
os.environ[settings.DEFAULT_AIIDA_PATH_VARIABLE] = current_path_variable

settings.glb_aiida_config_folder = current_config_path
settings.set_configuration_directory(current_config_path)

Check warning on line 193 in src/aiida/manage/tests/pytest_fixtures.py

View check run for this annotation

Codecov / codecov/patch

src/aiida/manage/tests/pytest_fixtures.py#L193

Added line #L193 was not covered by tests
configuration.CONFIG = current_config
if current_profile:
aiida_manager.load_profile(current_profile.name, allow_switch=True)
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 glb_aiida_config_folder
from aiida.manage.configuration.settings import get_configuration_directory
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 @@ -203,7 +203,7 @@ class Model(BaseModel, defer_build=True):
filepath: str = Field(
title='Directory of the backend',
description='Filepath of the directory in which to store data for this backend.',
default_factory=lambda: str(glb_aiida_config_folder / 'repository' / f'sqlite_dos_{uuid4().hex}'),
default_factory=lambda: str(get_configuration_directory() / 'repository' / f'sqlite_dos_{uuid4().hex}'),
)

@field_validator('filepath')
Expand Down
10 changes: 5 additions & 5 deletions tests/manage/configuration/test_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ def test_environment_variable_not_set(chdir_tmp_path, monkeypatch):

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


@pytest.mark.filterwarnings('ignore:Creating AiiDA configuration folder')
Expand All @@ -83,7 +83,7 @@ def test_environment_variable_set_single_path_without_config_folder(tmp_path):
# 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 settings.glb_aiida_config_folder == config_folder
assert settings.get_configuration_directory() == config_folder


@pytest.mark.filterwarnings('ignore:Creating AiiDA configuration folder')
Expand All @@ -99,7 +99,7 @@ def test_environment_variable_set_single_path_with_config_folder(tmp_path):
# 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 settings.glb_aiida_config_folder == config_folder
assert settings.get_configuration_directory() == config_folder


@pytest.mark.filterwarnings('ignore:Creating AiiDA configuration folder')
Expand All @@ -119,7 +119,7 @@ def test_environment_variable_path_including_config_folder(tmp_path):
# 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 settings.glb_aiida_config_folder == config_folder
assert settings.get_configuration_directory() == config_folder


@pytest.mark.filterwarnings('ignore:Creating AiiDA configuration folder')
Expand All @@ -142,7 +142,7 @@ def test_environment_variable_set_multiple_path(tmp_path):
# 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 settings.glb_aiida_config_folder == config_folder
assert settings.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 @@ -49,15 +49,15 @@ def test_merge_deprecated_yaml(tmp_path):

# Store the current configuration instance and config directory path
current_config = configuration.CONFIG
current_config_path = current_config.dirpath
current_config_path = pathlib.Path(current_config.dirpath)
current_profile_name = configuration.get_profile().name

try:
get_manager().unload_profile()
configuration.CONFIG = None

# Create a temporary folder, set it as the current config directory path
settings.glb_aiida_config_folder = str(tmp_path)
settings.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 @@ -86,7 +86,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()
settings.glb_aiida_config_folder = current_config_path
settings.set_configuration_directory(current_config_path)
configuration.CONFIG = current_config
load_profile(current_profile_name)

Expand Down

0 comments on commit c9a7c04

Please sign in to comment.