diff --git a/aiida/backends/djsite/db/models.py b/aiida/backends/djsite/db/models.py index 0cf3803d5f..5ff18cb74d 100644 --- a/aiida/backends/djsite/db/models.py +++ b/aiida/backends/djsite/db/models.py @@ -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 diff --git a/aiida/backends/sqlalchemy/models/user.py b/aiida/backends/sqlalchemy/models/user.py index 63b4a3a822..cbf4eee562 100644 --- a/aiida/backends/sqlalchemy/models/user.py +++ b/aiida/backends/sqlalchemy/models/user.py @@ -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()) diff --git a/aiida/orm/authinfo.py b/aiida/orm/authinfo.py index 17b02135f6..45ff97960e 100644 --- a/aiida/orm/authinfo.py +++ b/aiida/orm/authinfo.py @@ -11,11 +11,14 @@ 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 @@ -23,9 +26,9 @@ 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 @@ -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): """ @@ -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) diff --git a/aiida/orm/backend.py b/aiida/orm/backend.py index 6b199b0361..3b03a4d3bf 100644 --- a/aiida/orm/backend.py +++ b/aiida/orm/backend.py @@ -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: @@ -43,18 +41,16 @@ 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 @@ -62,9 +58,9 @@ def logs(self): @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 @@ -72,9 +68,33 @@ def users(self): @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 diff --git a/aiida/orm/implementation/django/authinfo.py b/aiida/orm/implementation/django/authinfo.py index 9c8fe9916d..4be64f9b99 100644 --- a/aiida/orm/implementation/django/authinfo.py +++ b/aiida/orm/implementation/django/authinfo.py @@ -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 @@ -25,9 +25,9 @@ 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) @@ -35,24 +35,21 @@ 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( @@ -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) @@ -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): @@ -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() diff --git a/aiida/orm/implementation/django/backend.py b/aiida/orm/implementation/django/backend.py index a85d2ba453..3523c16d1c 100644 --- a/aiida/orm/implementation/django/backend.py +++ b/aiida/orm/implementation/django/backend.py @@ -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): diff --git a/aiida/orm/implementation/django/user.py b/aiida/orm/implementation/django/user.py index e109b48038..2cce542ab9 100644 --- a/aiida/orm/implementation/django/user.py +++ b/aiida/orm/implementation/django/user.py @@ -17,6 +17,7 @@ class DjangoUserCollection(UserCollection): + def create(self, email, first_name='', last_name='', institution=''): """ Create a user with the provided email address @@ -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 @@ -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): diff --git a/aiida/orm/implementation/sqlalchemy/authinfo.py b/aiida/orm/implementation/sqlalchemy/authinfo.py index 0351dc5447..bb5ced4319 100644 --- a/aiida/orm/implementation/sqlalchemy/authinfo.py +++ b/aiida/orm/implementation/sqlalchemy/authinfo.py @@ -8,9 +8,9 @@ # For further information please visit http://www.aiida.net # ########################################################################### from aiida.backends.sqlalchemy.models.authinfo import DbAuthInfo -from aiida.orm.authinfo import AuthInfoCollection, AuthInfo from aiida.common import exceptions from aiida.common.utils import type_check +from aiida.orm.authinfo import AuthInfoCollection, AuthInfo from . import computer as computers from . import user as users @@ -18,6 +18,7 @@ class SqlaAuthInfoCollection(AuthInfoCollection): + def create(self, computer, user): return SqlaAuthInfo(self, computer, user) @@ -25,9 +26,9 @@ def get(self, computer, user): """ Return a SqlaAuthInfo given a computer and a user - :param computer: A Computer or DbComputer instance - :param user: A User or DbUser instance - :return: a SqlaAuthInfo 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 @@ -43,7 +44,7 @@ def get(self, computer, user): aiidauser_id=user.id, ).one() - return self._from_dbmodel(authinfo) + return self.from_dbmodel(authinfo) except NoResultFound: raise exceptions.NotExistent( "The aiida user {} is not configured to use computer {}".format( @@ -54,17 +55,15 @@ def get(self, computer, user): "computer {}! Only one configuration is allowed".format( user.email, computer.name)) - def _from_dbmodel(self, dbmodel): - return SqlaAuthInfo._from_dbmodel(self, dbmodel) + def from_dbmodel(self, dbmodel): + return SqlaAuthInfo.from_dbmodel(dbmodel, self.backend) class SqlaAuthInfo(AuthInfo): - """ - AuthInfo implementation for SQLAlchemy - """ + """AuthInfo implementation for SQLAlchemy.""" @classmethod - def _from_dbmodel(cls, backend, dbmodel): + def from_dbmodel(cls, dbmodel, backend): type_check(dbmodel, DbAuthInfo) authinfo = SqlaAuthInfo.__new__(cls) super(SqlaAuthInfo, authinfo).__init__(backend) @@ -74,19 +73,14 @@ def _from_dbmodel(cls, backend, dbmodel): def __init__(self, backend, computer, user): """ Construct an SqlaAuthInfo - """ - from aiida.orm.computer import Computer + :param computer: a Computer instance + :param user: a User instance + :return: an AuthInfo object associated with the given computer and user + """ super(SqlaAuthInfo, self).__init__(backend) - type_check(user, users.SqlaUser) - - # Takes care of always getting a Computer instance from a DbComputer, Computer or string - dbcomputer = Computer.get(computer).dbcomputer - # user.email exists both for DbUser and User, so I'm robust w.r.t. the type of what I get - dbuser = user.dbuser - self._dbauthinfo = utils.ModelWrapper( - DbAuthInfo(dbcomputer=dbcomputer, aiidauser=dbuser)) + self._dbauthinfo = utils.ModelWrapper(DbAuthInfo(dbcomputer=computer.dbcomputer, aiidauser=user.dbuser)) @property def dbauthinfo(self): @@ -95,9 +89,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() diff --git a/aiida/orm/implementation/sqlalchemy/backend.py b/aiida/orm/implementation/sqlalchemy/backend.py index a5cbff8ae8..c7ee379ce6 100644 --- a/aiida/orm/implementation/sqlalchemy/backend.py +++ b/aiida/orm/implementation/sqlalchemy/backend.py @@ -17,9 +17,9 @@ class SqlaBackend(Backend): def __init__(self): - self._logs = log.SqlaLogCollection() - self._users = user.SqlaUserCollection() - self._authinfos = authinfo.SqlaAuthInfoCollection() + self._logs = log.SqlaLogCollection(self) + self._users = user.SqlaUserCollection(self) + self._authinfos = authinfo.SqlaAuthInfoCollection(self) @property def logs(self): diff --git a/aiida/orm/implementation/sqlalchemy/user.py b/aiida/orm/implementation/sqlalchemy/user.py index cc90f08c8c..c0b536ead6 100644 --- a/aiida/orm/implementation/sqlalchemy/user.py +++ b/aiida/orm/implementation/sqlalchemy/user.py @@ -8,6 +8,7 @@ # For further information please visit http://www.aiida.net # ########################################################################### from aiida.backends.sqlalchemy.models.user import DbUser +from aiida.common.utils import type_check from aiida.orm.user import User, UserCollection from aiida.utils.email import normalize_email @@ -15,6 +16,7 @@ class SqlaUserCollection(UserCollection): + def create(self, email, first_name='', last_name='', institution=''): """ Create a user with the provided email address @@ -24,9 +26,6 @@ def create(self, email, first_name='', last_name='', institution=''): """ return SqlaUser(self, normalize_email(email), first_name, last_name, institution) - def from_dbmodel(self, dbuser): - return SqlaUser._from_dbmodel(self, dbuser) - def find(self, email=None, id=None): # Constructing the default query dbuser_query = DbUser.query @@ -45,17 +44,18 @@ def find(self, email=None, id=None): users.append(self.from_dbmodel(dbuser)) return users + def from_dbmodel(self, dbmodel): + return SqlaUser.from_dbmodel(dbmodel, self.backend) + class SqlaUser(User): - @classmethod - def _from_dbmodel(cls, backend, dbuser): - if not isinstance(dbuser, DbUser): - raise ValueError("Expected a DbUser. Object of a different" - "class was given as argument.") + @classmethod + def from_dbmodel(cls, dbmodel, backend): + type_check(dbmodel, DbUser) user = cls.__new__(cls) super(SqlaUser, 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): diff --git a/aiida/orm/log.py b/aiida/orm/log.py index ced6eab7df..44f7a967eb 100644 --- a/aiida/orm/log.py +++ b/aiida/orm/log.py @@ -10,6 +10,7 @@ from abc import abstractmethod, abstractproperty, ABCMeta from collections import namedtuple from aiida.utils import timezone +from .backend import Collection, CollectionEntry ASCENDING = 1 DESCENDING = -1 @@ -17,7 +18,7 @@ OrderSpecifier = namedtuple("OrderSpecifier", ['field', 'direction']) -class LogCollection(object): +class LogCollection(Collection): """ This class represents the collection of logs and can be used to create and retrieve logs. @@ -103,7 +104,7 @@ def delete_many(self, filter): pass -class Log(object): +class Log(CollectionEntry): __metaclass__ = ABCMeta @abstractproperty diff --git a/aiida/orm/user.py b/aiida/orm/user.py index c272bd56c9..df35dada5f 100644 --- a/aiida/orm/user.py +++ b/aiida/orm/user.py @@ -15,11 +15,12 @@ from aiida.common.hashing import is_password_usable from aiida.common import exceptions +from .backend import Collection, CollectionEntry __all__ = ['User', 'UserCollection'] -class UserCollection(object): +class UserCollection(Collection): """ The collection of users stored in a backend """ @@ -125,7 +126,7 @@ def all(self): return [_[0] for _ in query.all()] -class User(object): +class User(CollectionEntry): """ This is the base class for User information in AiiDA. An implementing backend needs to provide a concrete version. @@ -136,16 +137,9 @@ class User(object): __metaclass__ = abc.ABCMeta - def __init__(self, backend): - self._backend = backend - def __str__(self): return self.email - @property - def backend(self): - return self._backend - @abc.abstractproperty def pk(self): pass