MetaSubsetTabModel

class MetaSubsetTabModel()

Bases: MetaModel

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

MetadataModel and ProteinModel both back a tab whose data comes from a MetaDatabaseLoader: the user picks an experiment and channels, writes a subset filter, and plots the rows that come back. This base holds the half of that work that is the same in both, so there is one copy to fix - the events-table lookups behind an event plot, and the subset filter file’s JSON.

It is the Model half of the MetaSubsetTab* family, alongside MetaSubsetTabController, MetaSubsetTabView and MetaSubsetTabControls. Its methods live here rather than on MetaModel because the three analysis tabs that read a timeseries rather than a database - RawData, EventAnalysis and Clustering - have no events table to query and no filter file to write, and inheriting the API would say they did.

A subclass still owes MetaModel its _init.

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

MetaSubsetTabModel.load_events_by_id(loader: str, db_ids: str, experiments_and_channels: Dict[str, List[int | None]] | None) → Generator | None

Load exactly the rows named by their database ids.

The e.id IN (...) clause is a WHERE-clause body, which is what load_event_data takes - it splices it in after its own WHERE, so a complete SELECT cannot be passed here.

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

  • db_ids (str) – the comma-separated primary keys to load

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

Returns:

a generator over the matching events, or None

Return type:

Optional[Generator]

MetaSubsetTabModel.load_filters(path: str) → Dict[str, str]

Read a saved set of subset filters back off disk.

The file is a plain JSON object of filter name to filter text, which is what save_filters() writes. Anything else is refused rather than half accepted: a list or a scalar would iterate into something, and the caller would be left holding filters it never asked for.

Every value has to be a string, and that check is load-bearing. A saved session is a JSON object too, so the container check alone let one through, and its entries then split two ways: a value that is falsy - an empty dict, null - builds a query with no WHERE clause at all, which validates cleanly and commits as <name>_assisted with no body, while a non-empty one raises TypeError out of the query builder and raises a modal. Half the file arrives as filters nobody wrote and half as dialogs, which is exactly the outcome the all-or-nothing rule exists to prevent. An empty string stays legal, because the add-filter dialog requires a name and not a body, so a file this application wrote can contain one.

Parameters:

path (str) – the file to read

Returns:

the filters the file holds, keyed by name

Return type:

Dict[str, str]

Raises:

ValueError – if the file does not hold a JSON object of filter text

MetaSubsetTabModel.resolve_event_ids(loader: str, event_ids: Sequence[int], exp_id: int | None, channel: int | None) → DataFrame | None

Look up the database ids of these event_id values, within scope.

The scope is why this query exists at all. event_id is unique only within an experiment and channel, so an unscoped match returns whichever channel’s row happens to share the number. The caller stops rather than widening the query when it cannot resolve an experiment; that guard stays there, because it is the half that has something to tell the user.

The projection is id alone. An event plot’s requested order is restored in the View from the event_id the loader itself reports on each event, so naming the column here as well buys nothing.

DECISIONS.md (2026-08-25) accepts the f-string interpolation: the database is a local file owned by the user running the app.

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

  • event_ids (Sequence[int]) – the event_id values to resolve

  • exp_id (Optional[int]) – the experiment’s database id, or None to leave it out of scope

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

Returns:

the matching rows, or None if the loader had nothing to say

Return type:

Optional[pd.DataFrame]

MetaSubsetTabModel.save_filters(path: str, filters: Mapping[str, str]) → None

Write a set of subset filters to disk as JSON.

Indented, because the file is meant to be readable and hand-editable - a filter is a WHERE clause somebody wrote.

Parameters:
  • path (str) – the file to write

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

Returns:

None

Return type:

None

Private Methods

Abstract Methods

These methods must be implemented by subclasses.

(none)