Repository: datalad/datalad

Issue:
Remove --proc-pre/post (replacement: result hooks)
This new feature is proposed in https://github.com/datalad/datalad/pull/3903

I propose to remove its predecessors, because their are (from my POV) completely unused, untested, and less capable (only dataset procedures, not any DataLad command). 

Discussion:

Patch:
diff --git a/datalad/cmdline/main.py b/datalad/cmdline/main.py
index 085d7909e..ea36b369a 100644
--- a/datalad/cmdline/main.py
+++ b/datalad/cmdline/main.py
@@ -175,30 +175,12 @@ def setup_parser(
         of the command; 'continue' works like 'ignore', but an error causes a
         non-zero exit code; 'stop' halts on first failure and yields non-zero exit
         code. A failure is any result with status 'impossible' or 'error'.""")
-    parser.add_argument(
-        '--proc-pre', dest='common_proc_pre',
-        nargs='+',
-        action='append',
-        metavar=('<PROCEDURE NAME>', 'ARGS'),
-        help="""Dataset procedure to run before the main command (see run-procedure
-        command for details). This option can be given more than once to run
-        multiple procedures in the order in which they were given.
-        It is important to specify the target dataset via the --dataset argument
-        of the main command."""),
-    parser.add_argument(
-        '--proc-post', dest='common_proc_post',
-        nargs='+',
-        action='append',
-        metavar=('<PROCEDURE NAME>', 'ARGS'),
-        help="""Like --proc-pre, but procedures are executed after the main command
-        has finished."""),
     parser.add_argument(
         '--cmd', dest='_', action='store_true',
         help="""syntactical helper that can be used to end the list of global
-        command line options before the subcommand label. Options like
-        --proc-pre can take an arbitrary number of arguments and may require
-        to be followed by a single --cmd in order to enable identification
-        of the subcommand.""")
+        command line options before the subcommand label. Options taking
+        an arbitrary number of arguments may require to be followed by a single
+        --cmd in order to enable identification of the subcommand.""")
 
     # yoh: atm we only dump to console.  Might adopt the same separation later on
     #      and for consistency will call it --verbose-level as well for now
diff --git a/datalad/interface/base.py b/datalad/interface/base.py
index dc87ac6d8..ff3685d69 100644
--- a/datalad/interface/base.py
+++ b/datalad/interface/base.py
@@ -681,7 +681,7 @@ class Interface(object):
             # XXX define or better get from elsewhere
             common_opts = ('change_path', 'common_debug', 'common_idebug', 'func',
                            'help', 'log_level', 'logger', 'pbs_runner',
-                           'result_renderer', 'proc_pre', 'proc_post', 'subparser')
+                           'result_renderer', 'subparser')
             argnames = [name for name in dir(args)
                         if not (name.startswith('_') or name in common_opts)]
         kwargs = {k: getattr(args, k) for k in argnames if is_api_arg(k)}
@@ -712,8 +712,6 @@ class Interface(object):
                 # eval_results can't distinguish between --report-{status,type}
                 # not specified via the CLI and None passed via the Python API.
                 kwargs['result_filter'] = res_filter
-            kwargs['proc_pre'] = args.common_proc_pre
-            kwargs['proc_post'] = args.common_proc_post
         try:
             ret = cls.__call__(**kwargs)
             if inspect.isgenerator(ret):
diff --git a/datalad/interface/common_opts.py b/datalad/interface/common_opts.py
index e02fd0ac4..d2da17f0d 100644
--- a/datalad/interface/common_opts.py
+++ b/datalad/interface/common_opts.py
@@ -308,15 +308,6 @@ eval_params = dict(
         that carries the result dictionaries of the failures in its `failed`
         attribute.""",
         constraints=EnsureChoice('ignore', 'continue', 'stop')),
-    proc_pre=Parameter(
-        doc="""DataLad procedure to run prior to the main command. The argument
-        a list of lists with procedure names and optional arguments.
-        Procedures are called in the order their are given in this list.
-        It is important to provide the respective target dataset to run a procedure
-        on as the `dataset` argument of the main command."""),
-    proc_post=Parameter(
-        doc="""Like `proc_pre`, but procedures are executed after the main command
-        has finished."""),
 )
 
 eval_defaults = dict(
@@ -325,6 +316,4 @@ eval_defaults = dict(
     result_renderer=None,
     result_xfm=None,
     on_failure='continue',
-    proc_pre=None,
-    proc_post=None,
 )
diff --git a/datalad/interface/run_procedure.py b/datalad/interface/run_procedure.py
index 6529ad38b..11827742f 100644
--- a/datalad/interface/run_procedure.py
+++ b/datalad/interface/run_procedure.py
@@ -275,26 +275,6 @@ class RunProcedure(Interface):
     - 'datalad.procedures.<NAME>.help'
       will be shown on `datalad run-procedure --help-proc NAME` to provide a
       description and/or usage info for procedure NAME
-
-    *Customize other commands with procedures*
-
-    On execution of any commands, DataLad inspects two additional
-    configuration settings:
-
-    - 'datalad.<name>.proc-pre'
-
-    - 'datalad.<name>.proc-post'
-
-    where '<name>' is the name of a DataLad command. Using this mechanism
-    DataLad can be instructed to run one or more procedures before or
-    after the execution of a given command. For example, configuring
-    a set of metadata types in any newly created dataset can be achieved
-    via:
-
-      % datalad -c 'datalad.create.proc-post=cfg_metadatatypes xmp image' create -d myds
-
-    As procedures run on datasets, it is necessary to explicitly identify
-    the target dataset via the -d (--dataset) option.
     """
     _params_ = dict(
         spec=Parameter(
diff --git a/datalad/interface/utils.py b/datalad/interface/utils.py
index ab5dee7a4..4c4278f86 100644
--- a/datalad/interface/utils.py
+++ b/datalad/interface/utils.py
@@ -33,18 +33,19 @@ import json
 
 # avoid import from API to not get into circular imports
 from datalad.utils import with_pathsep as _with_sep  # TODO: RF whenever merge conflict is not upon us
-from datalad.utils import path_startswith
-from datalad.utils import path_is_subpath
-from datalad.utils import assure_unicode
-from datalad.utils import getargspec
+from datalad.utils import (
+    path_startswith,
+    path_is_subpath,
+    assure_unicode,
+    getargspec,
+    get_wrapped_class,
+)
 from datalad.support.gitrepo import GitRepo
 from datalad.support.exceptions import IncompleteResultsError
 from datalad import cfg as dlcfg
 from datalad.dochelpers import exc_str
 
 
-from datalad.support.constraints import Constraint
-
 from datalad.ui import ui
 import datalad.support.ansi_colors as ac
 
@@ -265,6 +266,16 @@ def discover_dataset_trace_to_targets(basepath, targetpaths, current_trace,
                 undiscovered_ds)
 
 
+def get_result_filter(fx):
+    """Wrap a filter into a helper to be able to accept additional
+    arguments, if the filter doesn't support it already"""
+    _fx = fx
+    if fx and not getargspec(fx).keywords:
+        def _fx(res, **kwargs):
+            return fx(res)
+    return _fx
+
+
 def eval_results(func):
     """Decorator for return value evaluation of datalad commands.
 
@@ -309,70 +320,49 @@ def eval_results(func):
     @wrapt.decorator
     def eval_func(wrapped, instance, args, kwargs):
         lgr.log(2, "Entered eval_func for %s", func)
-        # for result filters and pre/post procedures
+        # for result filters
         # we need to produce a dict with argname/argvalue pairs for all args
         # incl. defaults and args given as positionals
         allkwargs = get_allargs_as_kwargs(wrapped, args, kwargs)
-        # determine class, the __call__ method of which we are decorating:
-        mod = sys.modules[wrapped.__module__]
-        command_class_name = wrapped.__qualname__.split('.')[-2]
-        _func_class = mod.__dict__[command_class_name]
-        lgr.debug("Determined class of decorated function: %s", _func_class)
+
+        # determine the command class associated with `wrapped`
+        wrapped_class = get_wrapped_class(wrapped)
 
         # retrieve common options from kwargs, and fall back on the command
         # class attributes, or general defaults if needed
         kwargs = kwargs.copy()  # we will pop, which might cause side-effect
         common_params = {
             p_name: kwargs.pop(
+                # go with any explicitly given default
                 p_name,
-                getattr(_func_class, p_name, eval_defaults[p_name]))
+                # otherwise determine the command class and pull any
+                # default set in that class
+                getattr(
+                    wrapped_class,
+                    p_name,
+                    # or the common default
+                    eval_defaults[p_name]))
             for p_name in eval_params}
+
         # short cuts and configured setup for common options
-        on_failure = common_params['on_failure']
         return_type = common_params['return_type']
+        result_filter = get_result_filter(common_params['result_filter'])
         # resolve string labels for transformers too
-        result_xfm = common_params['result_xfm']
-        if result_xfm in known_result_xfms:
-            result_xfm = known_result_xfms[result_xfm]
+        result_xfm = known_result_xfms.get(
+            common_params['result_xfm'],
+            # use verbatim, if not a known label
+            common_params['result_xfm'])
         result_renderer = common_params['result_renderer']
         # TODO remove this conditional branch entirely, done outside
         if not result_renderer:
             result_renderer = dlcfg.get('datalad.api.result-renderer', None)
         # look for potential override of logging behavior
         result_log_level = dlcfg.get('datalad.log.result-level', None)
-        # wrap the filter into a helper to be able to pass additional arguments
-        # if the filter supports it, but at the same time keep the required interface
-        # as minimal as possible. Also do this here, in order to avoid this test
-        # to be performed for each return value
-        result_filter = common_params['result_filter']
-        _result_filter = result_filter
-        if result_filter:
-            if isinstance(result_filter, Constraint):
-                _result_filter = result_filter.__call__
-            if getargspec(_result_filter).keywords:
-                def _result_filter(res):
-                    return result_filter(res, **allkwargs)
-
-        def _get_procedure_specs(param_key=None, cfg_key=None, ds=None, proc_cfg=None):
-            lgr.log(2, "Getting procedure specs for %s of %s", param_key, ds)
-            spec = common_params.get(param_key, None)
-            if spec is not None:
-                # this is already a list of lists
-                return spec
-
-            spec = proc_cfg.get(cfg_key, None)
-            if spec is None:
-                return None
-            elif not isinstance(spec, tuple):
-                spec = [spec]
-            return [shlex.split(s) for s in spec]
 
         # query cfg for defaults
-        cmdline_name = cls2cmdlinename(_func_class)
-        dataset_arg = allkwargs.get('dataset', None)
-
         # .is_installed and .config can be costly, so ensure we do
         # it only once. See https://github.com/datalad/datalad/issues/3575
+        dataset_arg = allkwargs.get('dataset', None)
         from datalad.distribution.dataset import Dataset
         ds = dataset_arg if isinstance(dataset_arg, Dataset) \
             else Dataset(dataset_arg) if dataset_arg else None
@@ -388,17 +378,6 @@ def eval_results(func):
             ds, source='local', overrides=dlcfg.overrides
         ) if ds and ds.is_installed() else dlcfg
 
-        proc_pre = _get_procedure_specs(
-            'proc_pre',
-            'datalad.{}.proc-pre'.format(cmdline_name),
-            ds=dataset_arg,
-            proc_cfg=proc_cfg)
-        proc_post = _get_procedure_specs(
-            'proc_post',
-            'datalad.{}.proc-post'.format(cmdline_name),
-            ds=dataset_arg,
-            proc_cfg=proc_cfg)
-
         # look for hooks
         hooks = get_jsonhooks_from_config(proc_cfg)
 
@@ -411,35 +390,26 @@ def eval_results(func):
             # track what actions were performed how many times
             action_summary = {}
 
-            if proc_pre and cmdline_name != 'run-procedure':
-                from datalad.interface.run_procedure import RunProcedure
-                for procspec in proc_pre:
-                    lgr.debug('Running configured pre-procedure %s', procspec)
-                    for r in _process_results(
-                            RunProcedure.__call__(
-                                procspec,
-                                dataset=dataset_arg,
-                                return_type='generator'),
-                            _func_class, action_summary,
-                            on_failure, incomplete_results,
-                            result_renderer, result_xfm, result_filter,
-                            result_log_level,
-                            **_kwargs):
-                        yield r
-
             # if a custom summary is to be provided, collect the results
             # of the command execution
             results = []
             do_custom_result_summary = result_renderer == 'tailored' \
-                and hasattr(_func_class, 'custom_result_summary_renderer')
+                and hasattr(wrapped_class, 'custom_result_summary_renderer')
 
             # process main results
             for r in _process_results(
+                    # execution
                     wrapped(*_args, **_kwargs),
-                    _func_class, action_summary,
-                    on_failure, incomplete_results,
-                    result_renderer, result_xfm, _result_filter,
-                    result_log_level, **_kwargs):
+                    wrapped_class,
+                    common_params['on_failure'],
+                    # bookkeeping
+                    action_summary,
+                    incomplete_results,
+                    # communication
+                    result_renderer,
+                    result_log_level,
+                    # let renderers get to see how a command was called
+                    allkwargs):
                 for hook, spec in hooks.items():
                     # run the hooks before we yield the result
                     # this ensures that they are executed before
@@ -453,31 +423,34 @@ def eval_results(func):
                         # loops, i.e. when a hook yields a result that
                         # triggers that same hook again
                         for hr in run_jsonhook(hook, spec, r, dataset_arg):
-                            yield hr
-                yield r
+                            # apply same logic as for main results, otherwise
+                            # any filters would only tackle the primary results
+                            # and a mixture of return values could happen
+                            if not keep_result(hr, result_filter, **allkwargs):
+                                continue
+                            hr = xfm_result(hr, result_xfm)
+                            # rationale for conditional is a few lines down
+                            if hr:
+                                yield hr
+                if not keep_result(r, result_filter, **allkwargs):
+                    continue
+                r = xfm_result(r, result_xfm)
+                # in case the result_xfm decided to not give us anything
+                # exclude it from the results. There is no particular reason
+                # to do so other than that it was established behavior when
+                # this comment was written. This will not affect any real
+                # result record
+                if r:
+                    yield r
+
                 # collect if summary is desired
                 if do_custom_result_summary:
                     results.append(r)
 
-            if proc_post and cmdline_name != 'run-procedure':
-                from datalad.interface.run_procedure import RunProcedure
-                for procspec in proc_post:
-                    lgr.debug('Running configured post-procedure %s', procspec)
-                    for r in _process_results(
-                            RunProcedure.__call__(
-                                procspec,
-                                dataset=dataset_arg,
-                                return_type='generator'),
-                            _func_class, action_summary,
-                            on_failure, incomplete_results,
-                            result_renderer, result_xfm, result_filter,
-                            result_log_level, **_kwargs):
-                        yield r
-
             # result summary before a potential exception
             # custom first
             if do_custom_result_summary:
-                _func_class.custom_result_summary_renderer(results)
+                wrapped_class.custom_result_summary_renderer(results)
             elif result_renderer == 'default' and action_summary and \
                     sum(sum(s.values()) for s in action_summary.values()) > 1:
                 # give a summary in default mode, when there was more than one
@@ -496,7 +469,7 @@ def eval_results(func):
 
         if return_type == 'generator':
             # hand over the generator
-            lgr.log(2, "Returning generator_func from eval_func for %s", _func_class)
+            lgr.log(2, "Returning generator_func from eval_func for %s", wrapped_class)
             return generator_func(*args, **kwargs)
         else:
             @wrapt.decorator
@@ -509,14 +482,14 @@ def eval_results(func):
                 # render summaries
                 if not result_xfm and result_renderer == 'tailored':
                     # cannot render transformed results
-                    if hasattr(_func_class, 'custom_result_summary_renderer'):
-                        _func_class.custom_result_summary_renderer(results)
+                    if hasattr(wrapped_class, 'custom_result_summary_renderer'):
+                        wrapped_class.custom_result_summary_renderer(results)
                 if return_type == 'item-or-list' and \
                         len(results) < 2:
                     return results[0] if results else None
                 else:
                     return results
-            lgr.log(2, "Returning return_func from eval_func for %s", _func_class)
+            lgr.log(2, "Returning return_func from eval_func for %s", wrapped_class)
             return return_func(generator_func)(*args, **kwargs)
 
     return eval_func(func)
@@ -541,10 +514,14 @@ def default_result_renderer(res):
 
 
 def _process_results(
-        results, cmd_class,
-        action_summary, on_failure, incomplete_results,
-        result_renderer, result_xfm, result_filter,
-        result_log_level, **kwargs):
+        results,
+        cmd_class,
+        on_failure,
+        action_summary,
+        incomplete_results,
+        result_renderer,
+        result_log_level,
+        allkwargs):
     # private helper pf @eval_results
     # loop over results generated from some source and handle each
     # of them according to the requested behavior (logging, rendering, ...)
@@ -601,10 +578,10 @@ def _process_results(
                 default=lambda x: str(x)))
         elif result_renderer == 'tailored':
             if hasattr(cmd_class, 'custom_result_renderer'):
-                cmd_class.custom_result_renderer(res, **kwargs)
+                cmd_class.custom_result_renderer(res, **allkwargs)
         elif hasattr(result_renderer, '__call__'):
             try:
-                result_renderer(res, **kwargs)
+                result_renderer(res, **allkwargs)
             except Exception as e:
                 lgr.warning('Result rendering failed for: %s [%s]',
                             res, exc_str(e))
@@ -621,18 +598,27 @@ def _process_results(
                 # first fail -> that's it
                 # raise will happen after the loop
                 break
+        yield res
 
-        if result_filter:
-            try:
-                if not result_filter(res):
-                    raise ValueError('excluded by filter')
-            except ValueError as e:
-                lgr.debug('not reporting result (%s)', exc_str(e))
-                continue
 
-        if result_xfm:
-            res = result_xfm(res)
-            if res is None:
-                continue
+def keep_result(res, rfilter, **kwargs):
+    if not rfilter:
+        return True
+    try:
+        if not rfilter(res, **kwargs):
+            # give the slightest indication which filter was employed
+            raise ValueError(
+                'excluded by filter {} with arguments {}'.format(rfilter, kwargs))
+    except ValueError as e:
+        # make sure to report the excluded result to massively improve
+        # debugging experience
+        lgr.debug('Not reporting result (%s): %s', exc_str(e), res)
+        return False
+    return True
 
-        yield res
+
+def xfm_result(res, xfm):
+    if not xfm:
+        return res
+
+    return xfm(res)
diff --git a/datalad/utils.py b/datalad/utils.py
index ce8e85029..9eb7f1bc0 100644
--- a/datalad/utils.py
+++ b/datalad/utils.py
@@ -2320,6 +2320,15 @@ def maybe_shlex_quote(val):
     return val if on_windows else shlex_quote(val)
 
 
+def get_wrapped_class(wrapped):
+    """Determine the command class a wrapped __call__ belongs to"""
+    mod = sys.modules[wrapped.__module__]
+    command_class_name = wrapped.__qualname__.split('.')[-2]
+    _func_class = mod.__dict__[command_class_name]
+    lgr.debug("Determined class of decorated function: %s", _func_class)
+    return _func_class
+
+
 # TODO whenever we feel ready for English kill the compat block below
 assure_tuple_or_list = ensure_tuple_or_list
 assure_iter = ensure_iter


Test patch:
diff --git a/datalad/core/local/tests/test_create.py b/datalad/core/local/tests/test_create.py
index e9b1fe2bc..044f8ebd4 100644
--- a/datalad/core/local/tests/test_create.py
+++ b/datalad/core/local/tests/test_create.py
@@ -385,24 +385,6 @@ def test_saving_prior(topdir):
     assert_in('ds2', ds1.subdatasets(result_xfm='relpaths'))
 
 
-@with_tempfile(mkdir=True)
-def test_create_withprocedure(path):
-    # first without
-    ds = create(path)
-    assert(not op.lexists(op.join(ds.path, 'README.rst')))
-    ds.remove()
-    assert(not op.lexists(ds.path))
-    # now for reals...
-    ds = create(
-        # needs to identify the dataset, otherwise post-proc
-        # procedure doesn't know what to run on
-        dataset=path,
-        proc_post=[['cfg_metadatatypes', 'xmp', 'datacite']])
-    assert_repo_status(path)
-    ds.config.reload()
-    eq_(ds.config['datalad.metadata.nativetype'], ('xmp', 'datacite'))
-
-
 @with_tempfile(mkdir=True)
 def test_create_withcfg(path):
     ds = create(
diff --git a/datalad/core/local/tests/test_resulthooks.py b/datalad/core/local/tests/test_resulthooks.py
index f4581a86c..3b0f03481 100644
--- a/datalad/core/local/tests/test_resulthooks.py
+++ b/datalad/core/local/tests/test_resulthooks.py
@@ -31,6 +31,8 @@ def test_basics(src, dst):
     (ds.pathobj / 'file1').write_text('some')
     ds.save()
     sub = ds.create('subds')
+    # second one for a result_xfm test below
+    ds.create('subds2')
     eq_(sub.config.get('datalad.metadata.nativetype'), None)
 
     # now clone the super
@@ -39,8 +41,6 @@ def test_basics(src, dst):
     # on install to have 'bids' listed as a metadata type
     clone.config.set(
         'datalad.result-hook.alwaysbids.call-json',
-        # the spec is like --proc-post/pre, but has the dataset to run on as
-        # the first element
         # string substitutions based on the result record are supported
         'run_procedure {{"dataset":"{path}","spec":"cfg_metadatatypes bids"}}',
         where='local',
@@ -88,11 +88,14 @@ def test_basics(src, dst):
             '{"type":["in", ["file"]],"action":"get","status":"notneeded"}',
             where='local',
         )
-    # TODO resetting of detached HEAD seem to come after the install result
-    # and wipes out the change
+    # setup done, now see if it works
     clone.get('subds')
     clone_sub = Dataset(clone.pathobj / 'subds')
     eq_(clone_sub.config.get('datalad.metadata.nativetype'), 'bids')
+    # now the same thing with a result_xfm, should make no difference
+    clone.get('subds2')
+    clone_sub2 = Dataset(clone.pathobj / 'subds2')
+    eq_(clone_sub2.config.get('datalad.metadata.nativetype'), 'bids')
 
     # hook auto-unlocks the file
     if not on_windows:
diff --git a/datalad/interface/tests/test_base.py b/datalad/interface/tests/test_base.py
index d238b97cc..c7dd0d994 100644
--- a/datalad/interface/tests/test_base.py
+++ b/datalad/interface/tests/test_base.py
@@ -43,8 +43,6 @@ def _new_args(**kwargs):
                 common_on_failure=None,  # ['ignore', 'continue', 'stop']
                 common_report_status=None,  # ['all', 'success', 'failure', 'ok', 'notneeded', 'impossible', 'error']
                 common_report_type=None,  # ['dataset', 'file']
-                common_proc_pre=None,
-                common_proc_post=None,
             ),
             kwargs
         )
@@ -151,4 +149,4 @@ def test_nagen():
 def test_nadict():
     d = nadict({1: 2})
     eq_(d[1], 2)
-    eq_(str(d[2]), NA_STRING)
\ No newline at end of file
+    eq_(str(d[2]), NA_STRING)
diff --git a/datalad/interface/tests/test_run_procedure.py b/datalad/interface/tests/test_run_procedure.py
index ba895d0e2..cd928e0e8 100644
--- a/datalad/interface/tests/test_run_procedure.py
+++ b/datalad/interface/tests/test_run_procedure.py
@@ -70,57 +70,6 @@ def test_dirty(path):
     assert_repo_status(ds.path)
 
 
-@known_failure_windows  #FIXME
-@with_tree(tree={
-    'code': {'datalad_test_proc.py': """\
-import sys
-import os.path as op
-from datalad.api import save, Dataset
-
-with open(op.join(sys.argv[1], 'fromproc.txt'), 'w') as f:
-    f.write('hello\\n')
-save(dataset=Dataset(sys.argv[1]), path='fromproc.txt')
-"""}})
-@with_tempfile
-def test_basics(path, super_path):
-    ds = Dataset(path).create(force=True)
-    ds.run_procedure('cfg_yoda')
-    assert_false(ds.repo.is_under_annex("README.md"))
-    # save the procedure
-    ds.save('code')
-    # configure dataset to look for procedures in its code folder
-    ds.config.add(
-        'datalad.locations.dataset-procedures',
-        'code',
-        where='dataset')
-    # commit this procedure config for later use in a clone:
-    ds.save(op.join('.datalad', 'config'))
-    # configure dataset to run the demo procedure prior to the clean command
-    ds.config.add(
-        'datalad.clean.proc-pre',
-        'datalad_test_proc',
-        where='local')
-    # run command that should trigger the demo procedure
-    ds.clean()
-    # look for traces
-    ok_file_has_content(op.join(ds.path, 'fromproc.txt'), 'hello\n')
-
-    # make a fresh dataset:
-    super = Dataset(super_path).create()
-    # configure dataset to run the demo procedure prior to the clean command
-    super.config.add(
-        'datalad.clean.proc-pre',
-        'datalad_test_proc',
-        where='local')
-    # 'super' doesn't know any procedures but should get to know one by
-    # installing the above as a subdataset
-    super.install('sub', source=ds.path)
-    # run command that should trigger the demo procedure
-    super.clean()
-    # look for traces
-    ok_file_has_content(op.join(super.path, 'fromproc.txt'), 'hello\n')
-
-
 @skip_if(cond=on_windows and cfg.obtain("datalad.repo.version") < 6)
 @with_tree(tree={
     'code': {'datalad_test_proc.py': """\
@@ -160,11 +109,6 @@ def test_procedure_discovery(path, super_path):
         'datalad.locations.dataset-procedures',
         'code',
         where='dataset')
-    # configure dataset to run the demo procedure prior to the clean command
-    ds.config.add(
-        'datalad.clean.proc-pre',
-        'datalad_test_proc',
-        where='dataset')
     ds.save(op.join('.datalad', 'config'))
 
     # run discovery on the dataset: