MetaSubsetTabController

class MetaSubsetTabController()

Bases: MetaController

Shared base for the Controllers of the two database-backed analysis tabs.

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

What a subclass inherits:

  • Experiment and column state. request_column_names and request_experiment_structure ask the loader and hand its answer to the View, which is the Model-to-View half of the mediation this layer exists for.

  • Filter validation. validate_filter and validate_raw_filter answer the View’s two validation requests by calling the loader directly, so a filter the database refuses is reported through _refuse_filter rather than vanishing. relay_query receives the query the loader built - or the debug message explaining why it could not - and commits, renames or refuses the pending filter accordingly.

  • Session state. get_session_state and restore_session_state override MetaController’s hooks so a tab’s subset filters survive a save and reload.

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 same convention MetaView and MetaController already follow for their shared code.

  • self.view and self.model, built in its _init() as MetaController requires.

ivar logger:

the module logger the shared methods below log under

Public Methods

Abstract Methods

These methods must be implemented by subclasses.

(none)

Concrete Methods

MetaSubsetTabController.filter_scatterplot(columns: Sequence[ndarray[tuple[int, ...], dtype[float64]]], log_flags: Sequence[bool], ax: Axes, axis_labels: Sequence[str], dataset_label: str) → None

Filter and log-scale a scatterplot’s columns, and hand them back to draw.

The View asks, this slot calls the Model, and the answer goes back through a setter. Shared by both subset tabs, whose copies were identical: the drawing context arrives and departs unchanged, so this marshals and does not interpret it.

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

  • log_flags (Sequence[bool]) – log-scale each column?

  • ax (Axes) – the axis object the View will draw on

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

  • dataset_label (str) – string to label the dataset

Returns:

None

Return type:

None

MetaSubsetTabController.get_session_state() → Dict[str, Any]

Include the view’s live subset filters in this tab’s session history entry.

Returns:

Extra state to serialize into this tab’s session history entry.

Return type:

Dict[str, Any]

MetaSubsetTabController.load_event_id_cache(loader: str, conditions: str | None, experiments_and_channels: Dict[str, List[int] | None] | None) → None

Fetch the event_id values a subset holds, for the navigation cache.

Shared by both subset tabs because _rebuild_event_id_cache is. A failed query leaves the View’s answer at None, which is what lets the caller tell “that did not run” from “the subset is empty” - leaving the previous call’s rows in place would make the two indistinguishable and rebuild the cache from them.

Parameters:
  • loader (str) – the database loader plugin’s key

  • conditions (Optional[str]) – the subset filter, or None for every row

  • experiments_and_channels (Optional[Dict[str, Optional[List[int]]]]) – the experiment and channel scope, or None

Returns:

None

Return type:

None

MetaSubsetTabController.load_event_plot_data(loader: str, event_ids: List[int], exp: str | None, channel: int | None, experiments_and_channels: Dict[str, List[int | None]] | None, action_label: str) → None

Resolve event_id values to database ids within scope and load those rows.

The whole chain runs here because only one place can run it end to end and say which part failed. It was three separate requests once, each answer parked on a View attribute and read back on the next statement; nothing outside the chain ever read the intermediate answers.

An experiment that does not resolve stops the plot rather than widening the query. event_id is unique only within an experiment and channel, so an unscoped match returns whichever channel’s row happens to share the number - which is the “plots the wrong subset” fault, not a harmless one.

action_label names what the caller is plotting - events, histograms - and appears only in the messages, which is the whole of what the two tabs’ copies of this method used to differ by.

Parameters:
  • loader (str) – the database loader plugin’s key

  • event_ids (List[int]) – the event_id values to plot, already snapped to the cache

  • exp (Optional[str]) – the experiment the events belong to, or None to leave it out of scope

  • channel (Optional[int]) – the channel the events belong to, or None for all channels

  • experiments_and_channels (Optional[Dict[str, List[Optional[int]]]]) – the scope handed on to load_event_data

  • action_label (str) – what the caller is plotting, for its messages

Returns:

None

Return type:

None

MetaSubsetTabController.load_filters(path: str, loader: str) → None

Read a saved filter file and hand its contents to the View.

A file that cannot be read, or that does not hold a JSON object, is reported here and nothing is handed back - so the tab’s existing filters are left exactly as they were rather than half replaced.

Parameters:
  • path (str) – the file the user chose

  • loader (str) – the loader the filters will be validated against

Returns:

None

Return type:

None

MetaSubsetTabController.relay_query(query: str, debug: str, table_name: str, intent: str | None = None, name: str | None = None, old_name: str | None = None, filter_text: str | None = None) → None

Relay a query and optional debug message to the view, handling optional filter intents.

One copy for both subset tabs, and it reaches into no View state: name, old_name and filter_text arrive as arguments, carried from the request that asked for the validation, and committing the result goes through view.commit_filter rather than an assignment into the View’s dict. Both matter for the same reason - a value held on the widget between asking and being answered is one the next request can read by mistake.

Parameters:
  • query (str) – SQL query string to display or execute.

  • debug (str) – Debug message to display if query is empty.

  • table_name (str) – Name of the table associated with the query.

  • intent (Optional[str]) – ‘validate_new_filter’, ‘validate_edited_filter’, or None when the query is only being displayed

  • name (Optional[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 (Optional[str]) – the expression to commit under name, as the user wrote it; the query built around it is displayed, not stored

Returns:

None

Return type:

None

MetaSubsetTabController.request_column_names(loader: str) → None

Fetch a loader’s column names and hand them to the View.

The same shape as the reader and loader channel look-ups, against MetaDatabaseLoader. An empty answer is logged rather than pushed: clearing the axis comboboxes would read as “this database has no columns” when what happened is that nobody could ask it.

Parameters:

loader (str) – the database loader plugin’s key

Returns:

None

Return type:

None

MetaSubsetTabController.request_experiment_structure(loader_name: str) → None

Fetch a loader’s experiment-and-channel structure and file it under its key.

The loader key was the bus’s ret_args here: the answer has to be filed under the loader it came from, and passing it forward explicitly is what replaces that. Channels are stringified for display, as they were.

Parameters:

loader_name (str) – the database loader plugin’s key

Returns:

None

Return type:

None

MetaSubsetTabController.restore_session_state(state: Dict[str, Any]) → None

Restore subset filters captured by get_session_state() onto the view.

Parameters:

state (Dict[str, Any]) – This tab’s session history entry, as previously written by get_session_state().

MetaSubsetTabController.save_filters(path: str, filters: Dict[str, str]) → None

Write the tab’s filters to the file the user chose.

A failure used to reach the log only, so a full disk or a read-only folder looked exactly like a successful save.

Parameters:
  • path (str) – the file the user chose

  • filters (Dict[str, str]) – the filters to write, keyed by name

Returns:

None

Return type:

None

MetaSubsetTabController.validate_filter(loader: str, filter_text: str, intent: str, name: str, old_name: str | None) → None

Validate an assisted subset filter by asking the loader to build a query.

A filter counts as valid if construct_metadata_query can build a query around it; the query itself is thrown away. The call is made here rather than from the widget because construct_metadata_query raises for a column it cannot map to a table, and a filter naming a column the database does not have has to reach the user rather than a log line.

The columns are resolved here rather than passed in by the View, and only events columns are asked for. The View used to hand over a hardcoded ["sublevel_current", "voltage", "duration"] - one column from each of the three tables, which forced the built query to join all three every time, even for a filter with no conditions at all. One events column yields exactly the joins the filter itself needs: none for duration < 300, one for a filter that really does reference sublevels or experiments.

Parameters:
  • loader (str) – the database loader plugin’s key

  • filter_text (str) – the filter expression to validate, without WHERE

  • intent (str) – validate_new_filter or validate_edited_filter, passed through to relay_query to say what to do with the answer

  • 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

Returns:

None

Return type:

None

MetaSubsetTabController.validate_raw_filter(loader: str, query: str, name: str, old_name: str | None) → None

Validate a raw subset filter, which the loader checks without building.

A raw filter is a complete SELECT the loader runs verbatim, so it is checked with validate_filter_query rather than by constructing a query around it. As on the assisted path, the call is made here so that a failure reaches the user rather than a log line.

LIMIT 0 is appended here rather than by the View. The clause is what makes the check cheap - the database parses and plans the filter without returning a row - and choosing it is knowing SQL, which a widget has no business doing.

Parameters:
  • loader (str) – the database loader plugin’s key

  • query (str) – the raw filter as the user wrote it, without a trailing semicolon

  • 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

Returns:

None

Return type:

None

Private Methods

Abstract Methods

These methods must be implemented by subclasses.

(none)