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 whatload_event_datatakes - it splices it in after its ownWHERE, so a completeSELECTcannot be passed here.- Parameters:
- 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 noWHEREclause at all, which validates cleanly and commits as<name>_assistedwith no body, while a non-empty one raisesTypeErrorout 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:
- 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_idvalues, within scope.The scope is why this query exists at all.
event_idis 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
idalone. An event plot’s requested order is restored in the View from theevent_idthe 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:
- Returns:
the matching rows, or None if the loader had nothing to say
- Return type:
Optional[pd.DataFrame]
Private Methods¶
Abstract Methods¶
These methods must be implemented by subclasses.
(none)