BaseDataPlugin

class BaseDataPlugin(settings: Optional[dict] = None)

Bases: ABC

This class, BaseDataPlugin, is an abstraction of the functionality and interface that is common to all data plugins. What this means practically is that there is a chain of inheritance: all data plugins inherits from their respective base class, all of which inherit from BaseDataPlugin.

It handles stuff like instantiating the plugins, constructing settings dictionaries, and sanity checking the inputs, as well as a handful of bookkeeping functions used by the poriscope GUI to manage interactions between the MVC architecture and the data plugins themselves - basically, anything that involves interaction with the nuts and bolts of the poriscope GUI.

What You Get by Inheriting from Base Data Plugin

Warning

You probably do not need to inherit directly from BaseDataPlugin, as this is a general base class for the specific base classes from which Data Plugins are built. If your intention is to build a data plugin that fits one of the existing subtypes, you should inherit instead refer to one of the following pages:

  1. Build a MetaReader subclass

  2. Building a MetaFilter subclass

  3. Building a MetaEventFinder subclass

  4. Building a MetaWriter subclass

  5. Build a MetaEventLoader subclass

  6. Build a MetaEventFitter subclass

  7. Build a MetaDatabaseWriter subclass

  8. Build a MetaDatabaseLoader subclass

If you are planning to build an entirely new type of Data Plugin not in the list above, we strongly suggest contacting the poriscope developers first.

As soon as you subclass a base class from BaseDataPlugin, the following happens:

  • The poriscope GUI will know how to interact with this plugin type, and will manage its relationship to other plugin classes on which it might depend

  • Your plugin will handle basic sanity checks on settings at instantiation without any extra work needed

  • Several abstract functions are defined that can be realized either at the base class or subclass level that define a common API for all data plugins

Note

For the most part, users will not have to worry much about anything in this base class, as all other abstract base classes for data plugins inherit from this one and will define the relevant interface at the subclass level. However, in the unlikely event that you are defining an entirely new class of data plugin, it will need to inherit from this base in order to fully integrate into poriscope. Because integrating a new base into poriscope requires registration in core app elements, it is strongly encouraged that you contact the repository managers before trying in order to assess whether there is a simpler solution.

Attributes:

logger (logging.Logger): Logger instance for logging messages. lock (threading.RLock): Per-instance reentrant lock, used by serialize_channel_operations() to serialize this plugin’s own operations across channels when it declares that it must not run concurrently. One lock per plugin instance, so two different plugins never contend with each other. A plugin needing process-wide serialization (a non-reentrant native library, say) must declare its own class-level lock rather than reusing this one - see WaveletFilter.

Public Methods

Abstract Methods

These methods must be implemented by subclasses.

abstractmethod BaseDataPlugin.close_resources(channel: int | None = None) None

Perform any actions necessary to gracefully close resources before app exit. If channel is not None, handle only that channel, else close all of them.

Parameters:

channel (Optional[int]) – channel ID

abstractmethod BaseDataPlugin.get_empty_settings(globally_available_plugins: Dict[str, List[str]] | None = None, standalone: bool = False) Dict[str, Dict[str, Any]]

Get a dict populated with keys needed to initialize the filter if they are not set yet. This dict must have the following structure, but Min, Max, and Options can be skipped or explicitly set to None if they are not used. Type is required; Value may be omitted or set to None, both meaning there is no default and the user must supply one. All values provided must be consistent with Type.

settings = {'Parameter 1': {'Type': <int, float, str, bool>,
                                 'Value': <value> or None,
                                 'Options': [<option_1>, <option_2>, ... ] or None,
                                 'Min': <min_value> or None,
                                 'Max': <max_value> or None
                                },
                ...
                }

The base implementations here do omit Value - a reader’s schema is literally {"Input File": {"Type": str}}. Note that a settings dict supplied back to apply_settings() does need every Value present, because _validate_param_types reads it by subscript; the GUI’s settings dialog fills them in before that point.

Run python scripts/check_plugin_schemas.py to check a schema you have written for self-consistency, or see poriscope.utils.settings_schema to call the same check directly.

Parameters:
  • globally_available_plugins (Optional[ Dict[str, List[str]]]) – a dict containing all data plugins that exist to date, keyed by metaclass. Must include “MetaReader” as a key, with explicitly set Type MetaReader.

  • standalone (bool) – False if this is called as part of a GUI, True otherwise. Default False

Returns:

the dict that must be filled in to initialize the filter

Return type:

Dict[str, Dict[str, Any]]

abstractmethod BaseDataPlugin.report_channel_status(channel: int | None = None, init: bool = False) str

Return a string detailing any pertinent information about the status of analysis conducted on a given channel

Parameters:
  • channel (Optional[int]) – channel ID

  • init (bool) – is the function being called as part of plugin initialization? Default False

Returns:

the status of the channel as a string

Return type:

str

abstractmethod BaseDataPlugin.reset_channel(channel: int | None = None) None

Perform any actions necessary to reset a channel to its starting state. If channel is not None, handle only that channel, else reset all of them.

Parameters:

channel (Optional[int]) – channel ID

Concrete Methods

BaseDataPlugin.apply_settings(settings: dict) None

Validate that settings are correct and reasonable, and set params if the check passes

Parameters:

settings (dict) – a dict containing the information needed

BaseDataPlugin.force_serial_channel_operations() bool

Purpose: Indicate whether operations on different channels must be serialized (not run in parallel).

For plugins that do not depend on other data plugins, by default this simply returns False, meaning that it is acceptable and thread-safe to run operations on different channels in different threads on this plugin. If such operation is not thread-safe, this function should be overridden to simply return True. In the case where your plugin depends on another plugin (for example, event finder plugins depend on reader plugins), then your plugin should defer thread safety considerations to the plugin on which it depends.

Returns:

True if only one channel can run at a time, False otherwise

Return type:

bool

BaseDataPlugin.get_dependents() Set[Tuple[str, str]]

Get the set of (metaclass, key) tuples representing this plugin’s dependents.

Returns:

Set of dependents

Return type:

Set[Tuple[str, str]]

BaseDataPlugin.get_key() str

Get the key used to identify this plugin within the global app scope

Returns:

the key of the reader

Return type:

str

BaseDataPlugin.get_parents() Set[Tuple[str, str]]

Get the set of (metaclass, key) tuples representing this plugin’s parents.

Returns:

Set of parents

Return type:

Set[Tuple[str, str]]

BaseDataPlugin.get_raw_settings() dict

Get a copy of the settings that were applied during initialization of the instance

The returned dict is a snapshot rather than a view: the outer dict, each parameter’s dict, and any list-valued entry within one - notably Options - are all copied. A caller that mutates what it gets back therefore cannot write into this plugin’s internal state. update_raw_settings() and replace_raw_settings_option() are the only supported writers.

The copy is deliberately shallow below that level rather than a copy.deepcopy(): Type entries hold classes, and Value can transiently hold a live plugin instance while settings are being applied, neither of which is safe or meaningful to deep-copy.

Returns:

a copy of the dict that must be filled in to initialize the plugin

Return type:

dict

BaseDataPlugin.register_dependent(metaclass: str, key: str) None

Record that another plugin, identified by (metaclass, key), depends on this one.

Parameters:
  • metaclass (str) – the name of the Meta* base class of the dependent plugin

  • key (str) – the unique key of the dependent plugin

BaseDataPlugin.register_parent(metaclass: str, key: str) None

Record that this plugin depends on another plugin, identified by (metaclass, key).

Parameters:
  • metaclass (str) – the name of the Meta* base class of the parent plugin

  • key (str) – the unique key of the parent plugin

BaseDataPlugin.replace_raw_settings_option(key: str, old_value: Any, new_value: Any) None

Replace one entry in a setting’s list of allowed options

Needed when a parent plugin is renamed: a dependent carries the parent’s key both as the Value and in the Options list of the setting named after the parent’s metaclass, and the list must track the rename or _validate_param_ranges() will later reject the new key as not being an allowed option. This exists as a method because get_raw_settings() hands out a copy, so a caller cannot maintain the list by mutating what it gets back.

Does nothing if the setting is absent or declares no options.

Parameters:
  • key (str) – the settings key whose options should be updated

  • old_value (Any) – the option to remove, if it is present

  • new_value (Any) – the option to add, if it is not already present

BaseDataPlugin.serialize_channel_operations() Iterator[None]

Hold this plugin’s own lock for the duration of the block, but only if the plugin declares that its channels must not run concurrently.

This is where force_serial_channel_operations() is actually enforced. The declaration is a statement about this instance - “my own operations must not overlap across channels” - so the lock taken is this instance’s lock. Two different plugin instances never contend with each other, and a plugin that returns False pays nothing.

Enforcement lives here, on the object that makes the declaration, rather than in the caller. It used to live in MetaModel, which asked the plugin over the signal bus and then handed its own model-scoped lock to the worker - and since every analysis tab builds its own model, two tabs driving the same plugin instance took different locks and ran it concurrently anyway, while unrelated plugins within one tab serialized against each other for nothing.

The lock is held across yield, so a generator guarded by this is serialized for its whole run and releases when it is exhausted or closed. discard_generator() closes spent generators explicitly so that release does not depend on garbage-collection timing.

Yield:

None; the block runs with the lock held if serialization was requested.

Ytype:

None

BaseDataPlugin.set_key(key: str) None

Set the key used to identify this plugin within the global app scope

Parameters:

key (str) – the key of the plugin

BaseDataPlugin.unregister_dependent(metaclass: str, key: str) None

Remove a previously registered dependent, identified by (metaclass, key), if present.

Parameters:
  • metaclass (str) – the name of the Meta* base class of the dependent plugin

  • key (str) – the unique key of the dependent plugin

BaseDataPlugin.unregister_parent(metaclass: str, key: str) None

Remove a previously registered parent, identified by (metaclass, key), if present.

Parameters:
  • metaclass (str) – the name of the Meta* base class of the parent plugin

  • key (str) – the unique key of the parent plugin

BaseDataPlugin.update_raw_settings(key: str, val: Any) None

Update raw settings when needed

Parameters:
  • key (str) – the settings key to update

  • val (Any) – the new value to store for that key

Private Methods

Abstract Methods

These methods must be implemented by subclasses.

abstractmethod BaseDataPlugin._finalize_initialization() None

Apply the provided paramters and intialize any internal structures needed Should Raise if initialization fails.

This function is called at the end of the class constructor to perform additional initialization specific to the algorithm being implemented. kwargs provided to the base class constructor are available as class attributes.

abstractmethod BaseDataPlugin._init() None

called at the start of base class initialization

abstractmethod BaseDataPlugin._validate_settings(settings: dict) None

Validate that the settings dict contains the correct information for use by the subclass.

Parameters:

settings (dict) – Parameters required to configure this plugin.

Raises:

ValueError – If the settings dict does not contain the correct information.

Concrete Methods

BaseDataPlugin.__enter__() BaseDataPlugin

Enter the context management. Return self to be used within a ‘with’ statement.

BaseDataPlugin.__exit__(exc_type: Type[BaseException] | None, exc_val: BaseException | None, exc_tb: TracebackType | None) None

Exit the context management. Close resources.

BaseDataPlugin.__init__(settings: dict | None = None) None

Construct the plugin and, if settings are provided, apply them immediately.

Parameters:

settings (Optional[dict]) – A dict specifying the parameters of the plugin to be created. Required keys depend on subclass. If None, the plugin is left unconfigured until apply_settings() is called.

BaseDataPlugin._resolve_metaclass_name(cls: type) str

Walk the MRO of a plugin class to find its direct Meta* base (e.g. MetaEventFinder, MetaReader), regardless of how many concrete-subclass layers sit between it and that base.

Using cls.__bases__[0] instead only works if cls subclasses its Meta* base directly. For a plugin that subclasses another concrete plugin (e.g. BoundedBlockageFinder(ClassicBlockageFinder), itself a subclass of MetaEventFinder), that would return the intermediate concrete class’s name instead, which does not match any key in DataPluginModel’s per-metaclass plugin registry.

Parameters:

cls (type) – the plugin class to resolve

Raises:

TypeError – if cls does not inherit from a Meta* base class

Returns:

the name of the Meta* base class cls ultimately derives from

Return type:

str

BaseDataPlugin._validate_param_ranges(settings: dict) None

Validate that every parameter has a value and that it lies within any declared bounds

A parameter with no value - no Value key, or Value: None - is rejected here rather than in _validate_param_types(), so that the user is told the value is required instead of being told it has the wrong type. This also keeps None away from the bound comparisons below, which would otherwise raise TypeError from a method whose contract promises ValueError.

Parameters:

settings (dict) – A dict specifying the parameters of the filter to be created. Required keys depend on subclass.

Raises:

ValueError – If a required value is missing, out of range, or not an allowed option

BaseDataPlugin._validate_param_types(settings: Dict[str, Setting]) None

Validate that the filter_params dict contains correct data types, but only checks primitives. More detailed parameter checking should follow a call to super() in an override.

A parameter with no value at all - either no Value key or Value: None - is left for _validate_param_ranges() to reject, which reports it as a missing required value rather than as a type error. Reading the key with .get matters: a schema straight out of get_empty_settings() legitimately omits Value, and subscripting it raised KeyError where the caller expected TypeError.

Parameters:

settings (Dict[str, Setting]) – A dict specifying the parameters of the filter to be created. Required keys depend on subclass.

Raises:

TypeError – If the filter_params parameters are of the wrong type