55A widget for manual editing of a domain's attributes.
66
77"""
8+ from __future__ import annotations
89import warnings
910from xml .sax .saxutils import escape
1011from itertools import zip_longest , repeat , chain
1718
1819import numpy as np
1920import pandas as pd
21+
2022from AnyQt .QtWidgets import (
2123 QWidget , QListView , QTreeView , QVBoxLayout , QHBoxLayout , QFormLayout ,
2224 QLineEdit , QAction , QActionGroup , QGroupBox ,
3537)
3638from AnyQt .QtCore import pyqtSignal as Signal , pyqtSlot as Slot
3739
40+ from orangecanvas .utils import assocf
3841from orangewidget .utils .listview import ListViewSearch
3942
4043import Orange .data
@@ -2008,9 +2011,17 @@ class Outputs:
20082011 class Error (widget .OWWidget .Error ):
20092012 duplicate_var_name = widget .Msg ("A variable name is duplicated." )
20102013
2014+ class Warning (widget .OWWidget .Warning ):
2015+ transform_restore_failed = widget .Msg (
2016+ "Failed to restore transform {} for column {}"
2017+ )
2018+ cat_mapping_does_not_apply = widget .Msg (
2019+ "Categories mapping for {} does not apply to current input"
2020+ )
2021+
20112022 settings_version = 4
20122023
2013- _domain_change_hints = Setting ({}, schema_only = True )
2024+ _domain_change_hints : dict = Setting ({}, schema_only = True )
20142025 _merge_dialog_settings = Setting ({}, schema_only = True )
20152026 output_table_name = Setting ("" , schema_only = True )
20162027
@@ -2101,8 +2112,8 @@ def clear(self):
21012112 self .data = None
21022113 self .variables_model .clear ()
21032114 self .clear_editor ()
2104-
21052115 self ._merge_dialog_settings = {}
2116+ self .Warning .clear ()
21062117
21072118 def reset_selected (self ):
21082119 """Reset the currently selected variable to its original state."""
@@ -2157,6 +2168,27 @@ def setup_model(self, data: Orange.data.Table):
21572168 for i , d in enumerate (columns ):
21582169 model .setData (model .index (i ), d , Qt .EditRole )
21592170
2171+ def _sanitize_transform (
2172+ self , var : Variable , trs : Sequence [Transform ]
2173+ ) -> Sequence [Transform ]:
2174+ def does_categories_mapping_apply (
2175+ var : Categorical , tr : CategoriesMapping ) -> bool :
2176+ return set (var .categories ) \
2177+ == set (ci for ci , _ in tr .mapping if ci is not None )
2178+ if isinstance (var , Categorical ):
2179+ trs_ = []
2180+ for tr in trs :
2181+ if isinstance (tr , CategoriesMapping ):
2182+ if does_categories_mapping_apply (var , tr ):
2183+ trs_ .append (tr )
2184+ else :
2185+ self .Warning .cat_mapping_does_not_apply (var .name )
2186+ else :
2187+ trs_ .append (tr )
2188+ return trs_
2189+ else :
2190+ return trs
2191+
21602192 def _restore (self ):
21612193 """
21622194 Restore the edit transform from saved state.
@@ -2167,15 +2199,19 @@ def _restore(self):
21672199 for i in range (model .rowCount ()):
21682200 midx = model .index (i , 0 )
21692201 coldesc = model .data (midx , Qt .EditRole ) # type: DataVector
2170- tr , key = self ._restore_transform (coldesc .vtype )
2171- if tr :
2172- model .setData (midx , tr , TransformRole )
2173- if first_key is None :
2174- first_key = key
2202+ res = self ._find_stored_transform (coldesc .vtype )
2203+ if res :
2204+ key , tr = res
2205+ if tr :
2206+ self ._store_transform (coldesc .vtype , tr , key )
2207+ tr = self ._sanitize_transform (coldesc .vtype , tr )
2208+ model .setData (midx , tr , TransformRole )
2209+ if first_key is None :
2210+ first_key = key
21752211 # Reduce the number of hints to MAX_HINTS, but keep all current hints
21762212 # Current hints start with `first_key`.
21772213 while len (hints ) > MAX_HINTS and \
2178- (key := next (iter (hints ))) is not first_key :
2214+ (key := next (iter (hints ))) != first_key :
21792215 del hints [key ] # pylint: disable=unsupported-delete-operation
21802216
21812217 # Restore the current variable selection
@@ -2236,8 +2272,9 @@ def _on_variable_changed(self):
22362272 self ._store_transform (var , transform )
22372273 self ._invalidate ()
22382274
2239- def _store_transform (self , var , transform , deconvar = None ):
2240- # type: (Variable, List[Transform]) -> None
2275+ def _store_transform (
2276+ self , var : Variable , transform : Iterable [Transform ], deconvar = None
2277+ ) -> None :
22412278 deconvar = deconvar or deconstruct (var )
22422279 # Remove the existing key (if any) to put the new one at the end,
22432280 # to make sure it comes after the sentinel
@@ -2246,25 +2283,32 @@ def _store_transform(self, var, transform, deconvar=None):
22462283 self ._domain_change_hints [deconvar ] = \
22472284 [deconstruct (t ) for t in transform ]
22482285
2249- def _restore_transform (self , var ):
2250- # type: (Variable) -> List[Transform]
2286+ def _find_stored_transform (
2287+ self , var : Variable
2288+ ) -> Tuple [tuple , Sequence [Transform ]] | None :
2289+ """Find stored transform for `var`."""
2290+ def reconstruct_transform (tr_ : list [tuple ]) -> list [Transform ]:
2291+ trs = []
2292+ for t in tr_ :
2293+ try :
2294+ trs .append (cast (Transform , reconstruct (* t )))
2295+ except (AttributeError , TypeError , NameError ):
2296+ self .Warning .transform_restore_failed (
2297+ str (t ), var .name , exc_info = True ,
2298+ )
2299+ return trs
2300+
2301+ hints = self ._domain_change_hints
22512302 key = deconstruct (var )
2252- tr_ = self ._domain_change_hints .get (key , [])
2253- tr = []
2303+ tr = hints .get (key ) # exact match
2304+ if tr is not None :
2305+ return key , reconstruct_transform (tr )
22542306
2255- for t in tr_ :
2256- try :
2257- tr .append (reconstruct (* t ))
2258- except (NameError , TypeError ) as err :
2259- warnings .warn (
2260- f"Failed to restore transform: { t } , { err } " ,
2261- UserWarning , stacklevel = 2
2262- )
2263- if tr :
2264- self ._store_transform (var , tr , key )
2265- else :
2266- key = None
2267- return tr , key
2307+ # match by name and type only
2308+ item = assocf (hints .items (),
2309+ lambda k : k [0 ] == key [0 ] and k [1 ][0 ] == var .name )
2310+ if item is not None :
2311+ return item [0 ], reconstruct_transform (item [1 ])
22682312
22692313 def _invalidate (self ):
22702314 self ._set_modified (True )
0 commit comments