MetaSubsetTabView

class MetaSubsetTabView()

Bases: MetaView

Shared base for the Views of the two subset-filtering analysis tabs.

MetadataView and ProteinView both drive a tab whose data comes from a MetaDatabaseLoader: the user picks an experiment and channels from a selection tree, builds a named subset filter over the queried columns, and plots or exports the rows that come back. The two were written by copy-paste and carried fifteen methods verbatim between them; this base holds that shared half so there is one copy to fix.

What a subclass inherits:

  • Query state. set_query, set_event_query and set_experiment_id receive the SQL and the scope the Controller resolved, and optionally echo it to the status panel.

  • Column and experiment state. update_available_columns and set_units keep the tab’s column comboboxes and axis labels in step with the loader’s description of the database.

  • Subset filters. subset_filters is the store, and commit_filter/get_subset_filters are how the Controller reaches it - it asks rather than assigning into the dict (DECISIONS.md, 2026-09-13). _delete_filter, replace_filter_item, update_filter_name, restore_subset_filters and on_raw_filter_validated keep the filter combobox in step with it, reaching the combobox through _subset_controls, and _delete_filter_by_name and _show_filter_info_dialog are the combobox’s own entry points into that set. _show_add_filter_dialog and show_edit_filter_dialog open the two filter dialogs and validate what they return, through _reject_non_select_raw_filter and the Controller; _save_filter and _load_filter write the filters to and read them back from a JSON file.

  • Experiment selection. show_selection_tree and request_experiment_structure drive the SelectionTree dialog and remember what was chosen per loader.

  • The selected filters. get_selected_filters reads the filter combobox through _subset_controls, the property each tab implements below.

  • The filtered event_id cache. _rebuild_event_id_cache queries every event_id matching the current filter and scope, sorted, and keeps the four current_*/filtered_event_ids values the staleness checks compare.

What a subclass owes it:

  • Its own logger = logging.getLogger(__name__), so that records made by the methods it defines itself stay attributed to its own module. Methods defined here log under this module, which is the convention MetaView already follows for its shared code.

  • ``_subset_controls``, a one-line property returning whatever name the tab holds its controls panel under, so the shared methods here can reach it.

  • The five abstract methods ``MetaView`` declares, unchanged - this base implements none of them.

subset_filters lives here rather than on a Model, and the test is who reads it: no Model does - query construction takes a single filter’s text as an argument - while sixteen reads on this side need it inside the same call, the eleven get_selected_filters on the plot paths among them. Moving it would install a field nothing below reads and turn those sixteen into round trips. See DECISIONS.md, 2026-09-13, which also records what was rejected.

ivar logger:

the module logger the shared methods below log under

ivar subset_filters:

named subset filters, filter name to SQL WHERE clause

ivar selected_experiment_and_channels_by_loader:

per-loader selection tree state

Public Methods

Abstract Methods

These methods must be implemented by subclasses.

(none)

Concrete Methods

MetaSubsetTabView.commit_filter(name: str, filter_text: str | None, old_name: str | None = None) → None

Store a validated subset filter under name, replacing old_name if given.

The Controller used to do this by assigning into subset_filters directly - three sites in relay_query, plus a fourth reading the dict back in get_session_state. The dict stays here, because nothing in the Model reads it and sixteen reads on this side need it synchronously (DECISIONS.md, 2026-09-13); what changes is that the Controller asks rather than mutates.

Only the dict is touched. The combobox is a separate step, because the two commit paths update it differently - an added filter goes through replace_filter_item and an edited one through update_filter_name.

Parameters:
  • name (str) – the name to store the filter under, suffix already applied

  • filter_text (Optional[str]) – the filter expression; None and “” both store “”

  • old_name (Optional[str]) – on an edit, the name being replaced; None when adding

Returns:

None

Return type:

None

MetaSubsetTabView.get_save_filename() → str

Open a file dialog for the user to choose a save location.

Returns:

Selected filename.

Return type:

str

MetaSubsetTabView.get_selected_filters() → dict

The filters the user has selected for the current plotting or export task.

Promoted from both subset tabs, whose copies differed only in the name each held its controls panel under, and otherwise verbatim - including reaching past the panel to its combobox.

Returns:

selected filter names mapped to their SQL WHERE clauses

Return type:

dict

MetaSubsetTabView.get_subset_filters() → Dict[str, str]

A copy of this tab’s named subset filters.

A copy rather than the live dict, because the caller is the session-state serializer and the filters go on changing after it has taken its snapshot.

Returns:

filter name to SQL WHERE clause

Return type:

Dict[str, str]

MetaSubsetTabView.on_raw_filter_validated(valid: bool, error_msg: str, name: str, old_name: str | None, filter_text: str) → None

Commit a raw SQL filter once the loader has said the query is valid.

Promoted from both subset tabs. Beyond the controls-panel name they diverged in one place, the same one the two filter dialogs diverged in and settled the same way: an invalid filter is reported in a modal, which was Metadata’s behaviour, rather than on the status panel.

Parameters:
  • valid (bool) – Whether the query is valid.

  • error_msg (str) – Error message if invalid.

  • name (str) – the name to commit the filter under, carried from the request

  • old_name (Optional[str]) – on an edit, the name being replaced; None when adding

  • filter_text (str) – the raw SQL to commit under name

Returns:

None

Return type:

None

MetaSubsetTabView.replace_filter_item(name: str) → None

Remove any existing filter item with the same name and add the new one.

Promoted from both subset tabs, whose copies differed only in the name each held its controls panel under.

Parameters:

name (str) – The name of the filter to (re)add.

Returns:

None

Return type:

None

MetaSubsetTabView.request_experiment_structure(loader_name: str) → None

Get a dict of all experiments and channels available in a specified MetaDatabaseLoader object.

Parameters:

loader_name (str) – the key of the loader

MetaSubsetTabView.restore_subset_filters(filters: Dict[str, str]) → None

Restore subset filters captured in a saved session.

Unlike _load_filter(), this does not re-validate the filters against a database loader, since they were already valid when the session was saved.

One copy for both subset tabs, reached through _subset_controls rather than each tab’s own name for its panel - which is the whole of what the two copies used to differ by.

Parameters:

filters (Dict[str, str]) – Mapping of filter name to filter expression to restore.

Returns:

None

Return type:

None

MetaSubsetTabView.set_event_data_generator(generator: Iterator[Dict[str, Any]]) → None

Set the event data generator for event-based plots.

Parameters:

generator (Iterator[Dict[str, Any]]) – A generator that yields event data.

MetaSubsetTabView.set_event_id_rows(rows: DataFrame | None) → None

Receive the event_id rows the filtered-event cache asked for.

Called by MetaSubsetTabController.load_event_id_cache only once the query has run, so None means “that did not happen” rather than “the previous answer still stands”.

Parameters:

rows (Optional[pd.DataFrame]) – the rows the loader returned, or None if the query failed

Returns:

None

Return type:

None

MetaSubsetTabView.set_event_plot_data_generator(generator: Iterator[Dict[str, Any]]) → None

Receive the generator over the events an event plot asked for.

Called by MetaSubsetTabController.load_event_plot_data only once the whole resolve-and-load chain has succeeded, so the caller reading it back on the next statement can treat None as “that chain did not finish” rather than as “the previous plot’s events are still good”.

Parameters:

generator (Iterator[Dict[str, Any]]) – a generator over the events the loader returned

Returns:

None

Return type:

None

MetaSubsetTabView.set_event_query(query: str) → None

Store the SQL query used to fetch event data for this tab’s subset.

Parameters:

query (str) – SQL query string for fetching event data.

MetaSubsetTabView.set_loaded_filters(new_filters: Dict[str, str], loader: str) → None

Append filters read from a file, rejecting the whole file on a name clash.

A filter whose name ends in _raw is stored as it is instead of being sent through construct_metadata_query, which builds a WHERE clause and so cannot validate a complete SELECT. Measured consequence of not bypassing it: the query is built anyway, with the raw SELECT spliced in after WHERE and an empty debug message, so the validation succeeds and the filter is committed as <name>_raw_assisted - renamed, and reclassified as an assisted filter.

Parameters:
  • new_filters (Dict[str, str]) – the filters the file held, keyed by name

  • loader (str) – the loader to validate against, empty if none is selected

Returns:

None

Return type:

None

MetaSubsetTabView.set_query(query: str, table_name: str) → None

Set the SQL query and table name used in plotting.

This does not echo the query to the status panel. What it received at filter-creation time was the validation query, which is not the query that pulls the subset - MetadataController._echo_applied_query shows that one instead, when the filter is actually applied.

Parameters:
  • query (str) – SQL query string.

  • table_name (str) – Name of the database table.

MetaSubsetTabView.set_scatterplot(columns: Sequence[ndarray[tuple[int, ...], dtype[float64]]], ax: Axes, axis_labels: Sequence[str], dataset_label: str) → None

Draw a scatterplot of two filtered columns.

The answering half of scatterplot_requested. The NaN and log filtering belongs to the Model: the values it drops never reach the axes, and the ones it keeps are exported with the plot.

Parameters:
  • columns (Sequence[npt.NDArray[np.float64]]) – the filtered x and y values

  • ax (Axes) – the axis object on which to plot

  • axis_labels (Sequence[str]) – the x and y axis labels, already formatted

  • dataset_label (str) – Label for the dataset.

Returns:

None

Return type:

None

MetaSubsetTabView.show_edit_filter_dialog(name: str, loader: str) → None

Open the dialog that edits a subset filter, and validate what it returns.

One copy for both subset tabs, and not abstract. The reason recorded for its being abstract - that each tab rebuilds its own filter widgets afterwards - was _delete_filter’s, not this method’s: neither copy touched a filter widget. They diverged in the same two places as _show_add_filter_dialog, resolved the same way.

Parameters:
  • name (str) – the filter to edit

  • loader (str) – the database loader the filter applies to

MetaSubsetTabView.show_selection_tree(structure: dict[str, list[str]], loader_name: str, selection: dict[str, list[str]] | None = None) → None

Displays the selection tree for a given loader using the full structure and current selection.

MetaSubsetTabView.update_available_columns(loader: str) → None

Request available columns from the database loader.

Parameters:

loader (str) – Name of the active database loader.

MetaSubsetTabView.update_filter_name(old_name: str, new_name: str) → None

Replace old filter name with new one in the ComboBox, removing any duplicates.

Promoted from both subset tabs, whose copies differed only in the name each held its controls panel under.

Parameters:
  • old_name (str) – The filter name being replaced.

  • new_name (str) – The filter name to display instead.

Returns:

None

Return type:

None

Private Methods

Abstract Methods

These methods must be implemented by subclasses.

abstract property MetaSubsetTabView._subset_controls: MetaSubsetTabControls

This tab’s controls panel, under a name the shared methods here can use.

Each tab stores the panel under a name of its own inside _build_controls and uses that name throughout, which is fine for tab-specific code; a method promoted up to this base cannot know it. A property rather than a second attribute assigned alongside, so there is still exactly one place the panel is held and no chance of the two names drifting apart.

Returns:

the panel built by _build_controls

Return type:

MetaSubsetTabControls