Attach a given data file with a model run. The attached file is
        identified by the resource identifier. If a resource with the given
        identifier already exists it will be overwritten.

        This operation wil copy the file 'filename' into the attachments
        directory for the model run with name 'resource_id'. If the resource id
        is not a valid file name an exception will be thrown.

        Parameters
        ----------
        identifier : string
            Unique model run identifier
        resource_id : string
            Unique attachment identifier
        filename : string
            Path to data file that is being attached. A copy of the file will
            be created
        mime_type : string, optional
            File Mime type

        Returns
        -------
        ModelRunHandle
            Modified model run handle or None if no run with given identifier
            exists

def create_data_file_attachment(self, identifier, resource_id, filename, mime_type=None):
        """Attach a given data file with a model run. The attached file is
        identified by the resource identifier. If a resource with the given
        identifier already exists it will be overwritten.

        This operation wil copy the file 'filename' into the attachments
        directory for the model run with name 'resource_id'. If the resource id
        is not a valid file name an exception will be thrown.

        Parameters
        ----------
        identifier : string
            Unique model run identifier
        resource_id : string
            Unique attachment identifier
        filename : string
            Path to data file that is being attached. A copy of the file will
            be created
        mime_type : string, optional
            File Mime type

        Returns
        -------
        ModelRunHandle
            Modified model run handle or None if no run with given identifier
            exists
        """
        # Get model run to ensure that it exists
        model_run = self.get_object(identifier)
        if model_run is None:
            return None
        # It is only possible to attach files to successful model run
        if not model_run.state.is_success:
            raise ValueError('cannot attach file to model run in state: ' + str(model_run.state))
        # The attachment will be written to a file with name resource_id
        # inside the model run's attachment directory. If the resource id
        # contains path components we will create sub-folders for them. If the
        # resource file exists it will be overwritten.
        target = os.path.abspath(
            os.path.join(model_run.attachment_directory, resource_id)
        )
        directory, local_name = os.path.split(target)
        # Make sure that the given resource identifier does not result in the
        # file bein palced in a directory other than the attachments directory.
        if directory != model_run.attachment_directory:
            raise ValueError('invalid resource identifier: ' + resource_id)
        # Create the attachments directory if it doesn't exist
        if not os.path.exists(model_run.attachment_directory):
            os.makedirs(model_run.attachment_directory)
        shutil.copyfile(filename, target)
        if mime_type is None:
            # Mime type is derived from the file name
            mime_type = 'text/plain'
            if filename.endswith('.csv'):
                mime_type = 'text/csv'
            elif filename.endswith('.tsv'):
                mime_type = 'text/tab-separated-values'
            elif filename.endswith('.gz'):
                mime_type = 'application/x-gzip'
            elif filename.endswith('.png'):
                mime_type = 'image/png'
            elif filename.endswith('.jpg'):
                mime_type = 'image/jpeg'
            elif filename.endswith('.gif'):
                mime_type = 'image/gif'
        # Update model run information in the database
        model_run.attachments[resource_id] = Attachment(
            resource_id,
            mime_type,
            os.path.getsize(target)
        )
        self.replace_object(model_run)
        # Return modified model run
        return model_run