コード例 #1
0
    def __init__(self, **kwargs):
        self._err_acc = ErrorAccumulator(self.CodeValidationError)
        self._code_spec = {}

        # code_type must go first
        for key in ['code_type']:
            self.__setattr__(key, kwargs.pop(key))

        # then set the rest
        for key, value in kwargs.items():
            self.__setattr__(key, value)
コード例 #2
0
ファイル: computer.py プロジェクト: CasperWA/aiida_core
    def __init__(self, **kwargs):
        self._computer_spec = {}
        self._err_acc = ErrorAccumulator(self.ComputerValidationError)

        for key, value in kwargs.items():
            self.__setattr__(key, value)
コード例 #3
0
ファイル: computer.py プロジェクト: CasperWA/aiida_core
class ComputerBuilder:  # pylint: disable=too-many-instance-attributes
    """Build a computer with validation of attribute combinations"""
    @staticmethod
    def from_computer(computer):
        """Create ComputerBuilder from existing computer instance.

        See also :py:func:`~ComputerBuilder.get_computer_spec`"""
        spec = ComputerBuilder.get_computer_spec(computer)
        return ComputerBuilder(**spec)

    @staticmethod
    def get_computer_spec(computer):
        """Get computer attributes from existing computer instance.

        These attributes can be used to create a new ComputerBuilder::

            spec = ComputerBuilder.get_computer_spec(old_computer)
            builder = ComputerBuilder(**spec)
            new_computer = builder.new()"""
        spec = {}
        spec['label'] = computer.label
        spec['description'] = computer.description
        spec['hostname'] = computer.get_hostname()
        spec['scheduler'] = computer.get_scheduler_type()
        spec['transport'] = computer.get_transport_type()
        spec['prepend_text'] = computer.get_prepend_text()
        spec['append_text'] = computer.get_append_text()
        spec['work_dir'] = computer.get_workdir()
        spec['shebang'] = computer.get_shebang()
        spec['mpirun_command'] = ' '.join(computer.get_mpirun_command())
        spec[
            'mpiprocs_per_machine'] = computer.get_default_mpiprocs_per_machine(
            )

        return spec

    def __init__(self, **kwargs):
        self._computer_spec = {}
        self._err_acc = ErrorAccumulator(self.ComputerValidationError)

        for key, value in kwargs.items():
            self.__setattr__(key, value)

    def validate(self, raise_error=True):
        """Validate the computer options."""
        return self._err_acc.result(
            raise_error=self.ComputerValidationError if raise_error else False)

    @with_dbenv()
    def new(self):
        """Build and return a new computer instance (not stored)"""
        from aiida.orm import Computer

        self.validate()

        # Will be used at the end to check if all keys are known
        passed_keys = set(self._computer_spec.keys())
        used = set()

        computer = Computer(name=self._get_and_count('label', used),
                            hostname=self._get_and_count('hostname', used))

        computer.set_description(self._get_and_count('description', used))
        computer.set_scheduler_type(self._get_and_count('scheduler', used))
        computer.set_transport_type(self._get_and_count('transport', used))
        computer.set_prepend_text(self._get_and_count('prepend_text', used))
        computer.set_append_text(self._get_and_count('append_text', used))
        computer.set_workdir(self._get_and_count('work_dir', used))
        computer.set_shebang(self._get_and_count('shebang', used))

        mpiprocs_per_machine = self._get_and_count('mpiprocs_per_machine',
                                                   used)
        # In the command line, 0 means unspecified
        if mpiprocs_per_machine == 0:
            mpiprocs_per_machine = None
        if mpiprocs_per_machine is not None:
            try:
                mpiprocs_per_machine = int(mpiprocs_per_machine)
            except ValueError:
                raise self.ComputerValidationError(
                    'Invalid value provided for mpiprocs_per_machine, '
                    'must be a valid integer')
            if mpiprocs_per_machine <= 0:
                raise self.ComputerValidationError(
                    'Invalid value provided for mpiprocs_per_machine, '
                    'must be positive')
            computer.set_default_mpiprocs_per_machine(mpiprocs_per_machine)

        mpirun_command_internal = self._get_and_count('mpirun_command',
                                                      used).strip().split(' ')
        if mpirun_command_internal == ['']:
            mpirun_command_internal = []
        computer._mpirun_command_validator(mpirun_command_internal)  # pylint: disable=protected-access
        computer.set_mpirun_command(mpirun_command_internal)

        # Complain if there are keys that are passed but not used
        if passed_keys - used:
            raise self.ComputerValidationError(
                'Unknown parameters passed to the ComputerBuilder: {}'.format(
                    ', '.join(sorted(passed_keys - used))))

        return computer

    def __getattr__(self, key):
        """Access computer attributes used to build the computer"""
        if not key.startswith('_'):
            try:
                return self._computer_spec[key]
            except KeyError:
                raise self.ComputerValidationError(key + ' not set')
        return None

    def _get(self, key):
        """
        Return a spec, or None if not defined

        :param key: name of a computer spec"""
        return self._computer_spec.get(key)

    def _get_and_count(self, key, used):
        """
        Return a spec, or raise if not defined.
        Moreover, add the key to the 'used' dict.

        :param key: name of a computer spec
        :param used: should be a set of keys that you want to track.
           ``key`` will be added to this set if the value exists in the spec and can be retrieved.
        """
        retval = self.__getattr__(key)
        # I first get a retval, so if I get an exception, I don't add it to the 'used' set
        used.add(key)
        return retval

    def __setattr__(self, key, value):
        if not key.startswith('_'):
            self._set_computer_attr(key, value)
        super().__setattr__(key, value)

    def _set_computer_attr(self, key, value):
        """Set a computer attribute if it passes validation."""
        backup = self._computer_spec.copy()
        self._computer_spec[key] = value
        success, _ = self.validate(raise_error=False)
        if not success:
            self._computer_spec = backup
            self.validate()

    class ComputerValidationError(Exception):
        """
        A ComputerBuilder instance may raise this

         * when asked to instanciate a code with missing or invalid computer attributes
         * when asked for a computer attibute that has not been set yet."""
        def __init__(self, msg):
            super().__init__()
            self.msg = msg

        def __str__(self):
            return self.msg

        def __repr__(self):
            return '<ComputerValidationError: {}>'.format(self)
コード例 #4
0
class CodeBuilder(object):
    """Build a code with validation of attribute combinations"""
    def __init__(self, **kwargs):
        self._err_acc = ErrorAccumulator(self.CodeValidationError)
        self._code_spec = {}

        # code_type must go first
        for key in ['code_type']:
            self.__setattr__(key, kwargs.pop(key))

        # then set the rest
        for key, value in kwargs.items():
            self.__setattr__(key, value)

    def validate(self, raise_error=True):
        self._err_acc.run(self.validate_code_type)
        self._err_acc.run(self.validate_upload)
        self._err_acc.run(self.validate_installed)
        return self._err_acc.result(
            raise_error=self.CodeValidationError if raise_error else False)

    @with_dbenv()
    def new(self):
        """Build and return a new code instance (not stored)"""
        self.validate()

        from aiida.orm import Code

        # Will be used at the end to check if all keys are known (those that are not None)
        passed_keys = set(k for k in self._code_spec.keys()
                          if self._code_spec[k] is not None)
        used = set()

        if self._get_and_count('code_type',
                               used) == self.CodeType.STORE_AND_UPLOAD:
            file_list = [
                os.path.realpath(os.path.join(self.code_folder, f))
                for f in os.listdir(self._get_and_count('code_folder', used))
            ]
            code = Code(local_executable=self._get_and_count(
                'code_rel_path', used),
                        files=file_list)
        else:
            code = Code(remote_computer_exec=(
                self._get_and_count('computer', used),
                self._get_and_count('remote_abs_path', used)))

        code.label = self._get_and_count('label', used)
        code.description = self._get_and_count('description', used)
        code.set_input_plugin_name(
            self._get_and_count('input_plugin', used).name)
        code.set_prepend_text(self._get_and_count('prepend_text', used))
        code.set_append_text(self._get_and_count('append_text', used))

        # Complain if there are keys that are passed but not used
        if passed_keys - used:
            raise self.CodeValidationError(
                'Unknown parameters passed to the CodeBuilder: {}'.format(
                    ', '.join(sorted(passed_keys - used))))

        return code

    @staticmethod
    def from_code(code):
        """Create CodeBuilder from existing code instance.

        See also :py:func:`~CodeBuilder.get_code_spec`
        """
        spec = CodeBuilder.get_code_spec(code)
        return CodeBuilder(**spec)

    @staticmethod
    def get_code_spec(code):
        """Get code attributes from existing code instance.

        These attributes can be used to create a new CodeBuilder::

            spec = CodeBuilder.get_code_spec(old_code)
            builder = CodeBuilder(**spec)
            new_code = builder.new()

        """
        spec = {}
        spec['label'] = code.label
        spec['description'] = code.description
        spec['input_plugin'] = code.get_input_plugin_name()
        spec['prepend_text'] = code.get_prepend_text()
        spec['append_text'] = code.get_append_text()

        if code.is_local():
            spec['code_type'] = CodeBuilder.CodeType.STORE_AND_UPLOAD
            spec['code_folder'] = code.get_code_folder()
            spec['code_rel_path'] = code.get_code_rel_path()
        else:
            spec['code_type'] = CodeBuilder.CodeType.ON_COMPUTER
            spec['computer'] = code.get_remote_computer()
            spec['remote_abs_path'] = code.get_remote_exec_path()

        return spec

    def __getattr__(self, key):
        """Access code attributes used to build the code"""
        if not key.startswith('_'):
            try:
                return self._code_spec[key]
            except KeyError:
                raise KeyError("Attribute '{}' not set".format(key))
        return None

    def _get(self, key):
        """
        Return a spec, or None if not defined

        :param key: name of a code spec
        """
        return self._code_spec.get(key)

    def _get_and_count(self, key, used):
        """
        Return a spec, or raise if not defined.
        Moreover, add the key to the 'used' dict.

        :param key: name of a code spec
        :param used: should be a set of keys that you want to track.
           ``key`` will be added to this set if the value exists in the spec and can be retrieved.
        """
        retval = self.__getattr__(key)
        # I first get a retval, so if I get an exception, I don't add it to the 'used' set
        used.add(key)
        return retval

    def __setattr__(self, key, value):
        if not key.startswith('_'):
            self._set_code_attr(key, value)
        super(CodeBuilder, self).__setattr__(key, value)

    def _set_code_attr(self, key, value):
        """Set a code attribute, if it passes validation.

        Checks compatibility with other code attributes.
        """
        # store only string of input plugin
        if key == 'input_plugin' and isinstance(value, PluginParamType):
            value = value.name

        backup = self._code_spec.copy()
        self._code_spec[key] = value
        success, _ = self.validate(raise_error=False)
        if not success:
            self._code_spec = backup
            self.validate()

    def validate_code_type(self):
        """Make sure the code type is set correctly"""
        if self._get('code_type') and self.code_type not in self.CodeType:
            raise self.CodeValidationError(
                'invalid code type: must be one of {}, not {}'.format(
                    list(self.CodeType), self.code_type))

    def validate_upload(self):
        """If the code is stored and uploaded, catch invalid on-computer attributes"""
        messages = []
        if self.is_local():
            if self._get('computer'):
                messages.append(
                    'invalid option for store-and-upload code: "computer"')
            if self._get('remote_abs_path'):
                messages.append(
                    'invalid option for store-and-upload code: "remote_abs_path"'
                )
        if messages:
            raise self.CodeValidationError('{}'.format(messages))

    def validate_installed(self):
        """If the code is on-computer, catch invalid store-and-upload attributes"""
        messages = []
        if self._get('code_type') == self.CodeType.ON_COMPUTER:
            if self._get('code_folder'):
                messages.append(
                    'invalid options for on-computer code: "code_folder"')
            if self._get('code_rel_path'):
                messages.append(
                    'invalid options for on-computer code: "code_rel_path"')
        if messages:
            raise self.CodeValidationError('{}'.format(messages))

    class CodeValidationError(Exception):
        """
        A CodeBuilder instance may raise this

         * when asked to instanciate a code with missing or invalid code attributes
         * when asked for a code attibute that has not been set yet
        """
        def __init__(self, msg):
            super(CodeBuilder.CodeValidationError, self).__init__()
            self.msg = msg

        def __str__(self):
            return self.msg

        def __repr__(self):
            return '<CodeValidationError: {}>'.format(self)

    def is_local(self):
        """Analogous to Code.is_local()"""
        return self.__getattr__('code_type') == self.CodeType.STORE_AND_UPLOAD

    # pylint: disable=too-few-public-methods
    class CodeType(enum.Enum):
        STORE_AND_UPLOAD = 'store in the db and upload'
        ON_COMPUTER = 'on computer'