Skip to content

Commit b10aed3

Browse files
committed
added SkippablePlugin mixin to allow plugins to be disabled/removed from a pipeline (Filter/Writer implement this mixin)
1 parent 29a64d3 commit b10aed3

8 files changed

Lines changed: 128 additions & 7 deletions

File tree

CHANGES.rst

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,13 @@
11
Changelog
22
=========
33

4+
0.2.16 (2025-04-08)
5+
-------------------
6+
7+
- filters and writers can be skipped now via the `--skip` flag, making it easy for external
8+
scripts to enable/disable pipeline components
9+
10+
411
0.2.15 (2025-03-28)
512
-------------------
613

README.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,12 @@ the following two methods:
5757
* `_apply_args(self, ns: argparse.Namespace)`: in this method, you need to
5858
transfer the parsed options into member variables of your plugin
5959

60+
The `SkippablePlugin` mixin, as implemented by `Filter` and `Writer`, allows
61+
the disabling of the plugin via the `--skip` flag. Plugins that were disabled
62+
this way, get automatically removed when calling the `args_to_objects` function.
63+
This is an easy way of turning on/off parts of a pipeline without having to
64+
remove complex filter/writer configurations.
65+
6066

6167
### Aliases
6268

setup.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ def _read(f):
3333
install_requires=[
3434
"setuptools",
3535
],
36-
version="0.2.15",
36+
version="0.2.16",
3737
author='Peter Reutemann',
3838
author_email='fracpete@waikato.ac.nz',
3939
entry_points={

src/seppl/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
from ._plugin import Plugin, PluginWithLogging, OutputProducer, InputConsumer, check_compatibility
22
from ._plugin import LoggingHandler, Initializable, init_initializable
33
from ._plugin import AliasSupporter, get_aliases, has_aliases, get_all_names
4+
from ._plugin import SkippablePlugin, add_skip_option
45
from ._args import split_cmdline, split_args, args_to_objects, is_help_requested, enumerate_plugins, escape_args, unescape_args, resolve_handler
56
from ._registry import Registry, MODE_DYNAMIC, MODE_EXPLICIT, MODES
67
from ._class_registry import ClassListerRegistry, get_class_lister, DEFAULT

src/seppl/_args.py

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
import shlex
33

44
from typing import List, Dict, Tuple, Iterable, Set, Optional
5-
from ._plugin import Plugin
5+
from ._plugin import Plugin, SkippablePlugin
66

77

88
def escape_args(args: List[str]) -> List[str]:
@@ -51,6 +51,7 @@ def split_cmdline(cmdline: str, unescape: bool = False) -> List[str]:
5151
result = unescape_args(result)
5252
return result
5353

54+
5455
def resolve_handler(search: str, handlers: Set[str], partial: bool = False) -> Optional[str]:
5556
"""
5657
Tries to find the "search" string among the handlers, exact and partial match.
@@ -127,6 +128,7 @@ def split_args(args: List[str], handlers: List[str], unescape: bool = False, par
127128
def args_to_objects(args: Dict[str, List[str]], valid_plugins: Dict[str, Plugin], allow_global_options: bool = False, allow_unknown_args: bool = False, unescape: bool = False) -> List[Plugin]:
128129
"""
129130
Instantiates the plugins from the parsed arguments dictionary.
131+
Automatically removes SkippablePlugin instances that are to be skipped.
130132
131133
:param args: the arguments dictionary generated by split_args
132134
:type args: dict
@@ -158,6 +160,18 @@ def args_to_objects(args: Dict[str, List[str]], valid_plugins: Dict[str, Plugin]
158160
if not allow_unknown_args and (len(unknown) > 0):
159161
raise Exception("Found unknown argument(s) for plugin '%s': %s" % (plugin.name(), str(unknown)))
160162
result.append(plugin)
163+
164+
# remove plugins to be skipped
165+
i = 0
166+
while i < len(result):
167+
try:
168+
if isinstance(result[i], SkippablePlugin) and result[i].is_skipped:
169+
result.pop(i)
170+
continue
171+
except:
172+
pass
173+
i += 1
174+
161175
return result
162176

163177

src/seppl/_plugin.py

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -340,3 +340,31 @@ def check_compatibility(plugins: List[Plugin], match_all=AnyData):
340340
"Output(s) of " + index1 + "/" + plugin1.name()
341341
+ " not compatible with input(s) of " + index2 + "/" + plugin2.name() + ": "
342342
+ classes_to_str(classes1) + " != " + classes_to_str(classes2))
343+
344+
345+
class SkippablePlugin:
346+
"""
347+
Mixin for plugins that can be disabled, i.e., get skipped in the pipeline.
348+
"""
349+
350+
@property
351+
def is_skipped(self) -> bool:
352+
"""
353+
Returns whether the plugin is to be skipped.
354+
355+
:return: True if to be skipped
356+
:rtype: bool
357+
"""
358+
return False
359+
360+
361+
def add_skip_option(parser: argparse.ArgumentParser, help_str: str = "Disables the plugin, removing it from the pipeline."):
362+
"""
363+
Adds the --skip option to the argument parser.
364+
365+
:param parser: the parser to update
366+
:type parser: argparse.ArgumentParser
367+
:param help_str: the help string to use
368+
:type help_str: str
369+
"""
370+
parser.add_argument("--skip", action="store_true", default=False, help=help_str, required=False)

src/seppl/io/_filter.py

Lines changed: 35 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,16 @@
11
import abc
2+
import argparse
23
from typing import List
34

45
from wai.logging import LOGGING_WARNING
5-
from seppl import InputConsumer, OutputProducer, PluginWithLogging, Initializable, init_initializable, Session, SessionHandler
6+
from seppl import InputConsumer, OutputProducer, PluginWithLogging, Initializable, init_initializable, Session, SessionHandler, SkippablePlugin, add_skip_option
67

78
FILTER_ACTION_KEEP = "keep"
89
FILTER_ACTION_DISCARD = "discard"
910
FILTER_ACTIONS = [FILTER_ACTION_KEEP, FILTER_ACTION_DISCARD]
1011

1112

12-
class Filter(PluginWithLogging, InputConsumer, OutputProducer, SessionHandler, Initializable, abc.ABC):
13+
class Filter(PluginWithLogging, InputConsumer, OutputProducer, SessionHandler, Initializable, SkippablePlugin, abc.ABC):
1314
"""
1415
Base class for filters.
1516
"""
@@ -26,6 +27,38 @@ def __init__(self, logger_name: str = None, logging_level: str = LOGGING_WARNING
2627
super().__init__(logger_name=logger_name, logging_level=logging_level)
2728
self._session = None
2829
self._last_input = None
30+
self.skip = False
31+
32+
def _create_argparser(self) -> argparse.ArgumentParser:
33+
"""
34+
Creates an argument parser. Derived classes need to fill in the options.
35+
36+
:return: the parser
37+
:rtype: argparse.ArgumentParser
38+
"""
39+
parser = super()._create_argparser()
40+
add_skip_option(parser)
41+
return parser
42+
43+
def _apply_args(self, ns: argparse.Namespace):
44+
"""
45+
Initializes the object with the arguments of the parsed namespace.
46+
47+
:param ns: the parsed arguments
48+
:type ns: argparse.Namespace
49+
"""
50+
super()._apply_args(ns)
51+
self.skip = ns.skip
52+
53+
@property
54+
def is_skipped(self) -> bool:
55+
"""
56+
Returns whether the plugin is to be skipped.
57+
58+
:return: True if to be skipped
59+
:rtype: bool
60+
"""
61+
return self.skip
2962

3063
@property
3164
def session(self) -> Session:

src/seppl/io/_writer.py

Lines changed: 35 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
11
import abc
2+
import argparse
23
from typing import Iterable
34

45
from wai.logging import LOGGING_WARNING
56

6-
from seppl import InputConsumer, PluginWithLogging
7-
from seppl import SessionHandler, Session
7+
from seppl import InputConsumer, PluginWithLogging, SessionHandler, Session, SkippablePlugin, add_skip_option
88

99

10-
class Writer(PluginWithLogging, InputConsumer, SessionHandler, abc.ABC):
10+
class Writer(PluginWithLogging, InputConsumer, SessionHandler, SkippablePlugin, abc.ABC):
1111
"""
1212
Ancestor of classes that write data.
1313
"""
@@ -24,6 +24,38 @@ def __init__(self, logger_name: str = None, logging_level: str = LOGGING_WARNING
2424
super().__init__(logger_name=logger_name, logging_level=logging_level)
2525
self._session = None
2626
self._last_input = None
27+
self.skip = False
28+
29+
def _create_argparser(self) -> argparse.ArgumentParser:
30+
"""
31+
Creates an argument parser. Derived classes need to fill in the options.
32+
33+
:return: the parser
34+
:rtype: argparse.ArgumentParser
35+
"""
36+
parser = super()._create_argparser()
37+
add_skip_option(parser)
38+
return parser
39+
40+
def _apply_args(self, ns: argparse.Namespace):
41+
"""
42+
Initializes the object with the arguments of the parsed namespace.
43+
44+
:param ns: the parsed arguments
45+
:type ns: argparse.Namespace
46+
"""
47+
super()._apply_args(ns)
48+
self.skip = ns.skip
49+
50+
@property
51+
def is_skipped(self) -> bool:
52+
"""
53+
Returns whether the plugin is to be skipped.
54+
55+
:return: True if to be skipped
56+
:rtype: bool
57+
"""
58+
return self.skip
2759

2860
@property
2961
def session(self) -> Session:

0 commit comments

Comments
 (0)