Skip to content

Commit

Permalink
Refactor ORM collection and collection entries to always take the bac…
Browse files Browse the repository at this point in the history
…kend

Certain ORM collection entries, such as the AuthInfo class, require an
instance of the backend class, to retrieve other backend collections
and or entries. To ensure that these get properly constructed the concept
is abstracted by the Collection and CollectionEntry class which will are
to be sub classed for the various ORM classes. These base classes will
ensure that the backend is passed at construction time and provides a
property to retrieve it.
  • Loading branch information
sphuber committed Aug 2, 2018
1 parent abd0b4a commit db14d57
Show file tree
Hide file tree
Showing 12 changed files with 121 additions and 123 deletions.
2 changes: 1 addition & 1 deletion aiida/backends/djsite/db/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@ class DbUser(AbstractBaseUser, PermissionsMixin):
def get_aiida_class(self):
from aiida.orm.implementation.django.user import DjangoUser
from aiida.orm.backend import construct_backend
return DjangoUser._from_dbmodel(construct_backend(), self)
return DjangoUser.from_dbmodel(self, construct_backend())


@python_2_unicode_compatible
Expand Down
2 changes: 1 addition & 1 deletion aiida/backends/sqlalchemy/models/user.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,4 +48,4 @@ def __str__(self):
def get_aiida_class(self):
from aiida.orm.implementation.sqlalchemy.user import SqlaUser
from aiida.orm.backend import construct_backend
return SqlaUser._from_dbmodel(construct_backend(), self)
return SqlaUser.from_dbmodel(self, construct_backend())
32 changes: 14 additions & 18 deletions aiida/orm/authinfo.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,21 +11,24 @@

from aiida.transport import TransportFactory
from aiida.common.exceptions import (ConfigurationError, MissingPluginError)
from .backend import Collection, CollectionEntry

__all__ = ['AuthInfo', 'AuthInfoCollection']


class AuthInfoCollection(object):
class AuthInfoCollection(Collection):
"""The collection of AuthInfo entries."""

__metaclass__ = abc.ABCMeta

@abc.abstractmethod
def create(self, computer, user):
"""
Create a AuthInfo given a computer and a user
:param computer: A Computer or DbComputer instance
:param user: A User or DbUser instance
:return: a AuthInfo object associated to the given computer and User
:param computer: a Computer instance
:param user: a User instance
:return: a AuthInfo object associated to the given computer and user
"""
pass

Expand All @@ -34,30 +37,24 @@ def get(self, computer, user):
"""
Return a AuthInfo given a computer and a user
:param computer: A Computer or DbComputer instance
:param user: A User or DbUser instance
:return: a AuthInfo object associated to the given computer and User, if any
:param computer: a Computer instance
:param user: a User instance
:return: a AuthInfo object associated to the given computer and user
:raise NotExistent: if the user is not configured to use computer
:raise sqlalchemy.orm.exc.MultipleResultsFound: if the user is configured
more than once to use the computer! Should never happen
more than once to use the computer! Should never happen
"""
pass


class AuthInfo(object):
class AuthInfo(CollectionEntry):
"""
Base class to map a DbAuthInfo, that contains computer configuration
specific to a given user (authorization info and other metadata, like
how often to check on a given computer etc.)
"""
__metaclass__ = abc.ABCMeta

def __init__(self, backend):
self._backend = backend

@property
def backend(self):
return self._backend
__metaclass__ = abc.ABCMeta

def pk(self):
"""
Expand Down Expand Up @@ -171,6 +168,5 @@ def get_transport(self):
raise ConfigurationError('No transport found for {} [type {}], message: {}'.format(
computer.hostname, computer.get_transport_type(), e.message))

params = dict(computer.get_transport_params().items() +
self.get_auth_params().items())
params = dict(computer.get_transport_params().items() + self.get_auth_params().items())
return ThisTransport(machine=computer.hostname, **params)
48 changes: 34 additions & 14 deletions aiida/orm/backend.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,9 @@

def construct_backend(backend_type=None):
"""
Construct a concrete backend instance based on the backend_type
or use the global backend value if not specified.
Construct a concrete backend instance based on the backend_type or use the global backend value if not specified.
:param backend_type: Get a backend instance based on the specified
type (or default)
:param backend_type: get a backend instance based on the specified type (or default)
:return: :class:`Backend`
"""
if backend_type is None:
Expand All @@ -43,38 +41,60 @@ def construct_backend(backend_type=None):


class Backend(object):
"""
The public interface that defines a backend factory that creates backend
specific concrete objects.
"""
"""The public interface that defines a backend factory that creates backend specific concrete objects."""

__metaclass__ = ABCMeta

@abstractproperty
def logs(self):
"""
Get an object that implements the logging utilities interface.
Return the collection of log entries
:return: An concrete log utils object
:return: the log collection
:rtype: :class:`aiida.orm.log.Log`
"""
pass

@abstractproperty
def users(self):
"""
Get the collection of all users for this backend
Return the collection of users
:return: The users collection
:return: the users collection
:rtype: :class:`aiida.orm.user.UserCollection`
"""
pass

@abstractproperty
def authinfos(self):
"""
Get the collection of authorisation information
Return the collection of authorisation information objects
:return: The authinfo collection
:return: the authinfo collection
:rtype: :class:`aiida.orm.authinfo.AuthInfoCollection`
"""
pass


class Collection(object):
"""Container class that represents a collection of entries of a particular backend entity."""

def __init__(self, backend):
self._backend = backend

@property
def backend(self):
"""Return the backend."""
return self._backend


class CollectionEntry(object):
"""Class that represents an entry within a collection of entries of a particular backend entity."""

def __init__(self, backend):
self._backend = backend

@property
def backend(self):
"""Return the backend."""
return self._backend
55 changes: 23 additions & 32 deletions aiida/orm/implementation/django/authinfo.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,10 @@
###########################################################################
import json

from aiida.backends.djsite.db.models import DbComputer, DbAuthInfo
from aiida.orm.authinfo import AuthInfo, AuthInfoCollection
from aiida.backends.djsite.db.models import DbAuthInfo
from aiida.common import exceptions
from aiida.common.utils import type_check
from aiida.orm.authinfo import AuthInfo, AuthInfoCollection

from . import computer as computers
from . import user as users
Expand All @@ -25,34 +25,31 @@ def create(self, computer, user):
"""
Create a AuthInfo given a computer and a user
:param computer: A Computer or DbComputer instance
:param user: A User or DbUser instance
:return: a AuthInfo object associated to the given computer and User
:param computer: a Computer instance
:param user: a User instance
:return: an AuthInfo object associated with the given computer and user
"""
return DjangoAuthInfo(self, computer, user)

def get(self, computer, user):
"""
Return a AuthInfo given a computer and a user
:param computer: A Computer or DbComputer instance
:param user: A User or DbUser instance
:return: a AuthInfo object associated to the given computer and User, if any
:param computer: a Computer instance
:param user: a User instance
:return: an AuthInfo object associated with the given computer and user
:raise NotExistent: if the user is not configured to use computer
:raise sqlalchemy.orm.exc.MultipleResultsFound: if the user is configured
more than once to use the computer! Should never happen
more than once to use the computer! Should never happen
"""
from django.core.exceptions import (ObjectDoesNotExist,
MultipleObjectsReturned)
from django.core.exceptions import ObjectDoesNotExist, MultipleObjectsReturned

try:
authinfo = DbAuthInfo.objects.get(
# converts from name, Computer or DbComputer instance to
# a DbComputer instance
dbcomputer=DbComputer.get_dbcomputer(computer),
dbcomputer=computer.dbcomputer,
aiidauser=user.id)

return self._from_dbmodel(authinfo)
return self.from_dbmodel(authinfo)
except ObjectDoesNotExist:
raise exceptions.NotExistent(
"The aiida user {} is not configured to use computer {}".format(
Expand All @@ -63,17 +60,15 @@ def get(self, computer, user):
"computer {}! Only one configuration is allowed".format(
user.email, computer.name))

def _from_dbmodel(self, dbmodel):
return DjangoAuthInfo._from_dbmodel(self, dbmodel)
def from_dbmodel(self, dbmodel):
return DjangoAuthInfo.from_dbmodel(dbmodel, self.backend)


class DjangoAuthInfo(AuthInfo):
"""
AuthInfo implementation for Django
"""
"""AuthInfo implementation for Django."""

@classmethod
def _from_dbmodel(cls, backend, dbmodel):
def from_dbmodel(cls, dbmodel, backend):
type_check(dbmodel, DbAuthInfo)
authinfo = cls.__new__(cls)
super(DjangoAuthInfo, authinfo).__init__(backend)
Expand All @@ -82,19 +77,15 @@ def _from_dbmodel(cls, backend, dbmodel):

def __init__(self, backend, computer, user):
"""
Construct a DjangoAuthInfo
Construct a DjangoAuthInfo.
:param computer: a Computer instance
:param user: a User instance
:return: an AuthInfo object associated with the given computer and user
"""
from aiida.orm.computer import Computer

super(DjangoAuthInfo, self).__init__(backend)
type_check(user, users.DjangoUser)

# Takes care of always getting a Computer instance from a DbComputer, Computer or string
dbcomputer = Computer.get(computer).dbcomputer

self._dbauthinfo = utils.ModelWrapper(
DbAuthInfo(dbcomputer=dbcomputer, aiidauser=user.dbuser))
self._dbauthinfo = utils.ModelWrapper(DbAuthInfo(dbcomputer=computer.dbcomputer, aiidauser=user.dbuser))

@property
def dbauthinfo(self):
Expand All @@ -103,9 +94,9 @@ def dbauthinfo(self):
@property
def is_stored(self):
"""
Is it already stored or not?
Return whether the AuthInfo is stored
:return: Boolean
:return: True if stored, False otherwise
"""
return self._dbauthinfo.is_saved()

Expand Down
6 changes: 3 additions & 3 deletions aiida/orm/implementation/django/backend.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,9 @@
class DjangoBackend(Backend):

def __init__(self):
self._logs = log.DjangoLogCollection()
self._users = user.DjangoUserCollection()
self._authinfos = authinfo.DjangoAuthInfoCollection()
self._logs = log.DjangoLogCollection(self)
self._users = user.DjangoUserCollection(self)
self._authinfos = authinfo.DjangoAuthInfoCollection(self)

@property
def logs(self):
Expand Down
18 changes: 10 additions & 8 deletions aiida/orm/implementation/django/user.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@


class DjangoUserCollection(UserCollection):

def create(self, email, first_name='', last_name='', institution=''):
"""
Create a user with the provided email address
Expand All @@ -30,9 +31,6 @@ def create(self, email, first_name='', last_name='', institution=''):
last_name=last_name,
institution=institution)

def from_dbmodel(self, dbuser):
return DjangoUser._from_dbmodel(self, dbuser)

def find(self, email=None, id=None):
# Constructing the default query
import operator
Expand All @@ -56,24 +54,28 @@ def find(self, email=None, id=None):
users.append(self.from_dbmodel(dbuser))
return users

def from_dbmodel(self, dbmodel):
return DjangoUser.from_dbmodel(dbmodel, self.backend)


class DjangoUser(User):

@classmethod
def _from_dbmodel(cls, backend, dbuser):
def from_dbmodel(cls, dbmodel, backend):
"""
Create a DjangoUser from a dbmodel instance
:param backend: The backend
:type backend: :class:`DjangoUserCollection`
:param dbuser: The dbuser instance
:type dbuser: :class:`aiida.backends.djsite.db.models.DbUser`
:param dbmodel: The dbmodel instance
:type dbmodel: :class:`aiida.backends.djsite.db.models.DbUser`
:return: A DjangoUser instance
:rtype: :class:`DjangoUser`
"""
type_check(dbuser, DbUser)
type_check(dbmodel, DbUser)
user = cls.__new__(cls)
super(DjangoUser, user).__init__(backend)
user._dbuser = utils.ModelWrapper(dbuser)
user._dbuser = utils.ModelWrapper(dbmodel)
return user

def __init__(self, backend, email, first_name, last_name, institution):
Expand Down
Loading

0 comments on commit db14d57

Please sign in to comment.