Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

👌 IMPROVE: Move default user caching to StorageBackend #5460

Merged
merged 6 commits into from
Apr 26, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion aiida/orm/groups.py
Original file line number Diff line number Diff line change
Expand Up @@ -155,7 +155,7 @@ def __init__(
raise ValueError('Group label must be provided')

backend = backend or get_manager().get_profile_storage()
user = user or users.User.collection(backend).get_default()
user = user or backend.default_user
type_check(user, users.User)
type_string = self._type_string

Expand Down
15 changes: 15 additions & 0 deletions aiida/orm/implementation/storage_backend.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
BackendQueryBuilder,
BackendUserCollection,
)
from aiida.orm.users import User
from aiida.repository.backend.abstract import AbstractRepositoryBackend

__all__ = ('StorageBackend',)
Expand Down Expand Up @@ -84,6 +85,7 @@ def __init__(self, profile: 'Profile') -> None:
"""
from aiida.orm.autogroup import AutogroupManager
self._profile = profile
self._default_user: Optional['User'] = None
self._autogroup = AutogroupManager(self)

@abc.abstractmethod
Expand Down Expand Up @@ -161,6 +163,19 @@ def nodes(self) -> 'BackendNodeCollection':
def users(self) -> 'BackendUserCollection':
"""Return the collection of users"""

@property
def default_user(self) -> Optional['User']:
"""Return the default user for the profile, if it has been created.

This is cached, since it is a frequently used operation, for creating other entities.
"""
from aiida.orm import QueryBuilder, User

if self._default_user is None and self.profile.default_user_email:
query = QueryBuilder(self).append(User, filters={'email': self.profile.default_user_email})
self._default_user = query.first(flat=True)
return self._default_user

@abc.abstractmethod
def query(self) -> 'BackendQueryBuilder':
"""Return an instance of a query builder implementation for this backend"""
Expand Down
2 changes: 1 addition & 1 deletion aiida/orm/nodes/node.py
Original file line number Diff line number Diff line change
Expand Up @@ -181,7 +181,7 @@ def __init__(
raise ValueError('the computer is not stored')

computer = computer.backend_entity if computer else None
user = user if user else User.collection(backend).get_default()
user = user if user else backend.default_user

if user is None:
raise ValueError('the user cannot be None')
Expand Down
22 changes: 1 addition & 21 deletions aiida/orm/users.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,10 +29,6 @@ class UserCollection(entities.Collection['User']):
def _entity_base_cls() -> Type['User']:
return User

def __init__(self, entity_class: Type['User'], backend: Optional['StorageBackend'] = None) -> None:
super().__init__(entity_class=entity_class, backend=backend)
self._default_user: Optional[User] = None
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The overridden constructor is now pointless, might as well get rid of it entirely


def get_or_create(self, email: str, **kwargs) -> Tuple[bool, 'User']:
"""Get the existing user with a given email address or create an unstored one

Expand All @@ -48,23 +44,7 @@ def get_or_create(self, email: str, **kwargs) -> Tuple[bool, 'User']:

def get_default(self) -> Optional['User']:
"""Get the current default user"""
if self._default_user is None:
email = self.backend.profile.default_user_email
if not email:
self._default_user = None

try:
self._default_user = self.get(email=email)
except (exceptions.MultipleObjectsError, exceptions.NotExistent):
self._default_user = None

return self._default_user

def reset(self) -> None:
"""
Reset internal caches (default user).
"""
self._default_user = None
return self.backend.default_user


class User(entities.Entity['BackendUser']):
Expand Down
21 changes: 7 additions & 14 deletions aiida/storage/psql_dos/backend.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@

from aiida.common.exceptions import ClosedStorage, IntegrityError
from aiida.manage.configuration.profile import Profile
from aiida.orm import User
from aiida.orm.entities import EntityTypes
from aiida.orm.implementation import BackendEntity, StorageBackend
from aiida.storage.log import STORAGE_LOGGER
Expand Down Expand Up @@ -113,8 +112,6 @@ def get_session(self) -> Session:
def close(self) -> None:
if self._session_factory is None:
return # the instance is already closed, and so this is a no-op
# reset the cached default user instance, since it will now have no associated session
User.collection(self).reset()
# close the connection
# pylint: disable=no-member
engine = self._session_factory.bind
Expand All @@ -137,15 +134,13 @@ def _clear(self, recreate_user: bool = True) -> None:

# save the default user
default_user_kwargs = None
if recreate_user:
default_user = User.collection(self).get_default()
if default_user is not None:
default_user_kwargs = {
'email': default_user.email,
'first_name': default_user.first_name,
'last_name': default_user.last_name,
'institution': default_user.institution,
}
if recreate_user and self.default_user is not None:
default_user_kwargs = {
'email': self.default_user.email,
'first_name': self.default_user.first_name,
'last_name': self.default_user.last_name,
'institution': self.default_user.institution,
}

# now clear the database
for table_name in (
Expand All @@ -158,8 +153,6 @@ def _clear(self, recreate_user: bool = True) -> None:
# restore the default user
if recreate_user and default_user_kwargs:
session.add(DbUser(**default_user_kwargs))
# clear aiida's cache of the default user
User.collection(self).reset()

# Clear the repository and reset the repository UUID
container = Container(self.profile.repository_path / 'container')
Expand Down
6 changes: 6 additions & 0 deletions tests/storage/psql_dos/test_backend.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
import pytest

from aiida.manage import get_manager
from aiida.orm import User


@pytest.fixture(scope='function')
Expand All @@ -23,6 +24,11 @@ def clear_storage_before_test(aiida_profile_clean): # pylint: disable=unused-ar
repository.maintain(live=False)


@pytest.mark.usefixtures('clear_storage_before_test')
def test_default_user():
assert isinstance(get_manager().get_profile_storage().default_user, User)


@pytest.mark.usefixtures('clear_storage_before_test')
def test_get_unreferenced_keyset():
"""Test the ``get_unreferenced_keyset`` method."""
Expand Down