docs: def archive(self, odir=None, aname=None, fmt=None, projectname=None,
                  experiments=None, current_project=False, no_append=False,
                  no_project_paths=False, exclude=None, keep_exp=False,
                  rm_project=False, dry_run=False, dereference=False, **kwargs):
          """
          Archive one or more experiments or a project instance
  
          This method may be used to archive experiments in order to minimize the
          amount of necessary configuration files
  
          Parameters
          ----------
          odir: str
              The path where to store the archive
          aname: str
              The name of the archive (minus any format-specific extension). If
              None, defaults to the projectname
          fmt: { 'gztar' | 'bztar' | 'tar' | 'zip' }
              The format of the archive. If None, it is tested whether an
              archived with the name specified by `aname` already exists and if
              yes, the format is inferred, otherwise ``'tar'`` is used
          projectname: str
              If provided, the entire project is archived
          experiments: str
              If provided, the given experiments are archived. Note that an error
              is raised if they belong to multiple project instances
          current_project: bool
              If True, `projectname` is set to the current project
          no_append: bool
              It True and the archive already exists, it is deleted
          no_project_paths: bool
              If True, paths outside the experiment directories are neglected
          exclude: list of str
              Filename patterns to ignore (see :func:`glob.fnmatch.fnmatch`)
          keep_exp: bool
              If True, the experiment directories are not removed and no
              modification is made in the configuration
          rm_project: bool
              If True, remove all the project files
          dry_run: bool
              If True, set, do not actually make anything
          dereference: bool
              If set, dereference symbolic links. Note: This is automatically set
              for ``fmt=='zip'``
          """
          fnmatch = glob.fnmatch.fnmatch
  
          def to_exclude(fname):
              if exclude and (fnmatch(exclude, fname) or
                              fnmatch(exclude, osp.basename(fname))):
                  return True
  
          def do_nothing(path, file_obj):
              return
  
          def tar_add(path, file_obj):
              if sys.version_info[:2] < (3, 7):
                  file_obj.add(path, self.relpath(path), exclude=to_exclude)
              else:
                  file_obj.add(path, self.relpath(path),
                               filter=lambda f: None if to_exclude(f) else f)
  
          def zip_add(path, file_obj):
              # ziph is zipfile handle
              for root, dirs, files in os.walk(path):
                  for f in files:
                      abs_file = os.path.join(root, f)
                      if not to_exclude(abs_file):
                          file_obj.write(abs_file, self.relpath(abs_file))
  
          self.app_main(**kwargs)
          logger = self.logger
          all_exps = self.config.experiments
          if current_project or projectname is not None:
              if current_project:
                  projectname = self.projectname
              experiments = list(
                  self.config.experiments.project_map[projectname])
              if not experiments:
                  raise ValueError(
                      "Could not find any unarchived experiment for %s" % (
                          projectname))
          elif experiments is None:
              experiments = [self.experiment]
          already_archived = list(filter(self.is_archived, experiments))
          if already_archived:
              raise ValueError(
                  "The experiments %s have already been archived or are not "
                  "existent!" % ', '.join(
                      already_archived))
          if projectname is None:
              projectnames = {all_exps[exp]['project'] for exp in experiments}
              if len(projectnames) > 1:
                  raise ValueError(
                      "Experiments belong to multiple projects: %s" % (
                          ', '.join(projectnames)))
              projectname = next(iter(projectnames))
  
          self.projectname = projectname
          self.experiment = experiments[-1]
  
          exps2archive = OrderedDict(
              (exp, all_exps[exp]) for exp in experiments)
          project_config = self.config.projects[projectname]
  
          ext_map, fmt_map = self._archive_extensions()
          if aname is None:
              aname = projectname
          if fmt is None:
              ext, fmt = next(
                  (t for t in fmt_map.items() if osp.exists(aname + t[0])),
                  ['.tar', 'tar'])
          else:
              ext = fmt_map[fmt]
          if odir is None:
              odir = getcwd()
          archive_name = osp.join(odir, aname + ext)
          exists = osp.exists(archive_name)
          if exists and no_append:
              logger.debug('Removing existing archive %s' % archive_name)
              os.remove(archive_name)
              exists = False
          elif exists and fmt not in ['tar', 'zip']:
              raise ValueError(
                  "'Cannot append to %s because this is only possible for 'tar' "
                  "and 'zip' extension. Not %s" % (archive_name, fmt))
          logger.info('Archiving to %s', archive_name)
  
          paths = self._get_all_paths(exps2archive)
          root_dir = self.config.projects[projectname]['root']
          check_path = partial(utils.dir_contains, root_dir)
          not_included = OrderedDict([
              (key, list(filterfalse(check_path, utils.safe_list(val))))
              for key, val in paths.items()])
  
          for key, key_paths in not_included.items():
              for p in key_paths:
                  logger.warn(
                      '%s for key %s lies outside the project directory and '
                      'will not be included in the archive!', p, key)
          modes = {'bztar': 'w:bz2', 'gztar': 'w:gz', 'tar': 'w', 'zip': 'w'}
          mode = 'a' if exists else modes[fmt]
          atype = 'zip' if fmt == 'zip' else 'tar'
          if dry_run:
              add_dir = do_nothing
              file_obj = None
          elif atype == 'zip':
              import zipfile
              add_dir = zip_add
              file_obj = zipfile.ZipFile(archive_name, mode)
          else:
              import tarfile
              add_dir = tar_add
              file_obj = tarfile.open(archive_name, mode,
                                      dereference=dereference)
          for exp in experiments:
              exp_dir = exps2archive[exp]['expdir']
              logger.debug('Adding %s', exp_dir)
              add_dir(exp_dir, file_obj)
  
          now = str(dt.datetime.now())  # current time
  
          # configuration directory
          config_dir = osp.join(root_dir, '.project')
          if not dry_run and not osp.exists(config_dir):
              os.makedirs(config_dir)
          for exp in experiments:
              conf_file = osp.join(config_dir, exp + '.yml')
              logger.debug('Store %s experiment config to %s', exp, conf_file)
              if not dry_run:
                  exps2archive[exp].setdefault('timestamps', {})
                  exps2archive[exp]['timestamps']['archive'] = now
                  with open(osp.join(config_dir, exp + '.yml'), 'w') as f:
                      ordered_yaml_dump(self.rel_paths(
                          copy.deepcopy(exps2archive[exp])), f)
          # project configuration file
          conf_file = osp.join(config_dir, '.project.yml')
          logger.debug('Store %s project config to %s', projectname, conf_file)
          if not dry_run:
              safe_dump(project_config, conf_file)
  
          logger.debug('Add %s to archive', config_dir)
          add_dir(config_dir, file_obj)
  
          if not no_project_paths:
              for dirname in os.listdir(root_dir):
                  if osp.basename(dirname) not in ['experiments', '.project']:
                      logger.debug('Adding %s', osp.join(root_dir, dirname))
                      add_dir(osp.join(root_dir, dirname), file_obj)
          if not keep_exp:
              for exp in experiments:
                  exp_dir = exps2archive[exp]['expdir']
                  logger.debug('Removing %s', exp_dir)
                  if not dry_run:
                      all_exps[exp] = a = Archive(archive_name)
                      a.project = projectname
                      a.time = now
                      shutil.rmtree(exp_dir)
          if rm_project:
              logger.debug('Removing %s', root_dir)
              if not dry_run:
                  shutil.rmtree(root_dir)
          if not dry_run:
              file_obj.close()