@@ -997,17 +997,18 @@ def requery(
997
997
self .rows .load_sort_settings (sort_settings )
998
998
self .rows .sort (self .table )
999
999
1000
- for row in self .rows :
1001
- # perform transform one row at a time
1002
- if self .transform is not None :
1003
- self .transform (self , row , TFORM_DECODE )
1000
+ # Perform transform one row at a time
1001
+ if self .transform is not None :
1002
+ self .rows = self .rows .apply (
1003
+ lambda row : self .transform (self , row , TFORM_DECODE ) or row , axis = 1
1004
+ )
1004
1005
1005
- # Strip trailing white space, as this is what sg[element].get() does, so we
1006
- # can have an equal comparison. Not the prettiest solution. Will look into
1007
- # this more on the PySimpleGUI end and make a follow-up ticket.
1008
- for k , v in row . items ():
1009
- if type ( v ) is str :
1010
- row [ k ] = v . rstrip ( )
1006
+ # Strip trailing white space, as this is what sg[element].get() does, so we
1007
+ # can have an equal comparison. Not the prettiest solution. Will look into
1008
+ # this more on the PySimpleGUI end and make a follow-up ticket.
1009
+ self . rows = self . rows . applymap (
1010
+ lambda x : x . rstrip () if isinstance ( x , str ) else x
1011
+ )
1011
1012
1012
1013
if select_first :
1013
1014
self .first (
@@ -1423,18 +1424,18 @@ def get_current_pk(self) -> int:
1423
1424
"""
1424
1425
return self .get_current (self .pk_column )
1425
1426
1426
- def get_current_row (self ) -> Union [ResultRow , None ]:
1427
+ def get_current_row (self ) -> Union [pd . Series , None ]:
1427
1428
"""
1428
1429
Get the row for the currently selected record of this table.
1429
1430
1430
- :returns: A `ResultRow` object
1431
+ :returns: A pandas Series object
1431
1432
"""
1432
- if self .rows :
1433
+ if not self .rows . empty :
1433
1434
# force the current_index to be in bounds!
1434
1435
# For child reparenting
1435
1436
self .current_index = self .current_index
1436
1437
1437
- return self .rows [self .current_index ]
1438
+ return self .rows . iloc [self .current_index ]
1438
1439
return None
1439
1440
1440
1441
def add_selector (
@@ -3054,7 +3055,10 @@ def update_elements(
3054
3055
disable = (
3055
3056
len (self [data_key ].rows ) == 0
3056
3057
or self ._edit_protect
3057
- or self [data_key ].get_current_row ().virtual
3058
+ or self [data_key ]
3059
+ .get_current_row ()
3060
+ .attrs .get ("virtual" , False )
3061
+ .iloc [0 ]
3058
3062
)
3059
3063
win [m ["event" ]].update (disabled = disable )
3060
3064
@@ -3183,6 +3187,13 @@ def update_elements(
3183
3187
else :
3184
3188
lst = []
3185
3189
for row in target_table .rows :
3190
+ print (
3191
+ row ,
3192
+ pk_column ,
3193
+ description ,
3194
+ row [pk_column ],
3195
+ row [description ],
3196
+ )
3186
3197
lst .append (ElementRow (row [pk_column ], row [description ]))
3187
3198
3188
3199
# Map the value to the combobox, by getting the description_column
@@ -5856,7 +5867,242 @@ def copy(self):
5856
5867
return ResultRow (self .row .copy (), virtual = self .virtual )
5857
5868
5858
5869
5859
- class ResultSet :
5870
+ import pandas as pd
5871
+
5872
+
5873
+ class ResultSet (pd .DataFrame ):
5874
+ """
5875
+ The ResultSet class is a generic result class so that working with the resultset of
5876
+ the different supported databases behave in a consistent manner. A `ResultSet` is a
5877
+ Pandas dataframe with some extra functionality to make working with abstracted
5878
+ database drivers easier.
5879
+
5880
+ ResultSets can be thought up as rows of information. Iterating through a ResultSet
5881
+ is very simple:
5882
+ ResultSet = driver.execute('SELECT * FROM Journal;')
5883
+ for row in rows:
5884
+ print(row['title'])
5885
+
5886
+ Note: The lastrowid is set by the caller, but by pysimplesql convention, the
5887
+ lastrowid should only be set after and INSERT statement is executed.
5888
+ """
5889
+
5890
+ SORT_NONE = 0
5891
+ SORT_ASC = 1
5892
+ SORT_DESC = 2
5893
+
5894
+ def __init__ (
5895
+ self ,
5896
+ rows : List [Dict [str , Any ]] = [],
5897
+ lastrowid : int = None ,
5898
+ exception : str = None ,
5899
+ column_info : ColumnInfo = None ,
5900
+ ) -> None :
5901
+ """
5902
+ Create a new ResultSet instance.
5903
+
5904
+ :param rows: a list of dicts representing a row of data, with each key being a
5905
+ column name
5906
+ :param lastrowid: The primary key of an inserted item.
5907
+ :param exception: If an exception was encountered during the query, it will be
5908
+ passed along here
5909
+ :column_info: a `ColumnInfo` object can be supplied so that column information
5910
+ can be accessed
5911
+ """
5912
+ super ().__init__ (rows )
5913
+ self .lastrowid = lastrowid
5914
+ self .exception = exception
5915
+ self .column_info = column_info
5916
+ self .sort_column = None
5917
+ self .sort_reverse = False
5918
+ self .attrs ["original_index" ] = self .index .copy () # Store the original index
5919
+ self .attrs ["virtual" ] = pd .Series (
5920
+ [False ] * len (self ), index = self .index
5921
+ ) # Store virtual flags for each row
5922
+
5923
+ def __str__ (self ):
5924
+ return str (self .to_dict (orient = "records" ))
5925
+
5926
+ def fetchone (self ) -> pd .Series :
5927
+ """
5928
+ Fetch the first record in the ResultSet.
5929
+
5930
+ :returns: A `pd.Series` object representing the row
5931
+ """
5932
+ return self .iloc [0 ] if len (self ) else pd .Series (dtype = object )
5933
+
5934
+ def fetchall (self ) -> ResultSet :
5935
+ """
5936
+ ResultSets don't actually support a fetchall(), since the rows are already
5937
+ returned. This is more of a comfort method that does nothing, for those that are
5938
+ used to calling fetchall().
5939
+
5940
+ :returns: The same ResultSet that called fetchall()
5941
+ """
5942
+ return self
5943
+
5944
+ def insert (self , row : dict , idx : int = None , virtual : bool = False ) -> None :
5945
+ """
5946
+ Insert a new virtual row into the `ResultSet`. Virtual rows are ones that exist
5947
+ in memory, but not in the database. When a save action is performed, virtual
5948
+ rows will be added into the database.
5949
+
5950
+ :param row: A dict representation of a row of data
5951
+ :param idx: The index where the row should be inserted (default to last index)
5952
+ :returns: None
5953
+ """
5954
+ row_series = pd .Series (row )
5955
+ if idx is None :
5956
+ idx = len (self )
5957
+ idx_label = self .index .max () + 1 if len (self ) > 0 else 0
5958
+ self .loc [idx_label ] = row_series
5959
+ self .attrs ["original_index" ] = self .attrs ["original_index" ].insert (
5960
+ idx , idx_label
5961
+ )
5962
+ self .attrs ["virtual" ].loc [idx_label ] = virtual
5963
+ self .sort_index ()
5964
+
5965
+ def purge_virtual (self ) -> None :
5966
+ """
5967
+ Purge virtual rows from the `ResultSet`.
5968
+
5969
+ :returns: None
5970
+ """
5971
+ virtual_rows = self .attrs ["virtual" ][self .attrs ["virtual" ]].index
5972
+ self .drop (virtual_rows , inplace = True )
5973
+ self .attrs ["original_index" ] = self .attrs ["original_index" ].drop (virtual_rows )
5974
+ self .attrs ["virtual" ] = self .attrs ["virtual" ].drop (virtual_rows )
5975
+
5976
+ def sort_by_column (self , column : str , table : str , reverse = False ) -> None :
5977
+ """
5978
+ Sort the `ResultSet` by column. Using the mapped relationships of the database,
5979
+ foreign keys will automatically sort based on the parent table's description
5980
+ column, rather than the foreign key number.
5981
+
5982
+ :param column: The name of the column to sort the `ResultSet` by
5983
+ :param table: The name of the table the column belongs to
5984
+ :param reverse: Reverse the sort; False = ASC, True = DESC
5985
+ :returns: None
5986
+ """
5987
+ # Target sorting by this ResultSet
5988
+ rows = self # search criteria is based on rows
5989
+ target_col = column # Looking in rows for this column
5990
+ target_val = column # to be equal to the same column in self.rows
5991
+
5992
+ # We don't want to sort by foreign keys directly - we want to sort by the
5993
+ # description column of the foreign table that the foreign key references
5994
+ rels = Relationship .get_relationships (table )
5995
+ for rel in rels :
5996
+ if column == rel .fk_column :
5997
+ rows = rel .frm [
5998
+ rel .parent_table
5999
+ ] # change the rows used for sort criteria
6000
+ target_col = rel .pk_column # change our target column to look in
6001
+ target_val = rel .frm [
6002
+ rel .parent_table
6003
+ ].description_column # and return the value in this column
6004
+ break
6005
+
6006
+ def get_sort_key (row ):
6007
+ try :
6008
+ return next (
6009
+ r [target_val ]
6010
+ for _ , r in rows .iterrows ()
6011
+ if r [target_col ] == row [column ]
6012
+ )
6013
+ except StopIteration :
6014
+ return None
6015
+
6016
+ try :
6017
+ self .sort_values (
6018
+ by = self .index , key = get_sort_key , ascending = not reverse , inplace = True
6019
+ )
6020
+ except KeyError :
6021
+ logger .debug (f"ResultSet could not sort by column { column } . KeyError." )
6022
+
6023
+ def sort_by_index (self , index : int , table : str , reverse = False ):
6024
+ """
6025
+ Sort the `ResultSet` by column index Using the mapped relationships of the
6026
+ database, foreign keys will automatically sort based on the parent table's
6027
+ description column, rather than the foreign key number.
6028
+
6029
+ :param index: The index of the column to sort the `ResultSet` by
6030
+ :param table: The name of the table the column belongs to
6031
+ :param reverse: Reverse the sort; False = ASC, True = DESC
6032
+ :returns: None
6033
+ """
6034
+ column = self .columns [index ]
6035
+ self .sort_by_column (column , table , reverse )
6036
+
6037
+ def store_sort_settings (self ) -> list :
6038
+ """
6039
+ Store the current sort settingg. Sort settings are just the sort column and
6040
+ reverse setting. Sort order can be restored with
6041
+ `ResultSet.load_sort_settings()`.
6042
+
6043
+ :returns: A list containing the sort_column and the sort_reverse
6044
+ """
6045
+ return [self .sort_column , self .sort_reverse ]
6046
+
6047
+ def load_sort_settings (self , sort_settings : list ) -> None :
6048
+ """
6049
+ Load a previously stored sort setting. Sort settings are just the sort columm
6050
+ and reverse setting.
6051
+
6052
+ :param sort_settings: A list as returned by `ResultSet.store_sort_settings()`
6053
+ """
6054
+ self .sort_column = sort_settings [0 ]
6055
+ self .sort_reverse = sort_settings [1 ]
6056
+
6057
+ def sort_reset (self ) -> None :
6058
+ """
6059
+ Reset the sort order to the original when this ResultSet was created. Each
6060
+ ResultRow has the original order stored.
6061
+
6062
+ :returns: None
6063
+ """
6064
+ self .sort_index (inplace = True )
6065
+
6066
+ def sort (self , table : str ) -> None :
6067
+ """
6068
+ Sort according to the internal sort_column and sort_reverse variables. This is a
6069
+ good way to re-sort without changing the sort_cycle.
6070
+
6071
+ :param table: The table associated with this ResultSet. Passed along to
6072
+ `ResultSet.sort_by_column()`
6073
+ :returns: None
6074
+ """
6075
+ if self .sort_column is None :
6076
+ self .sort_reset ()
6077
+ else :
6078
+ self .sort_by_column (self .sort_column , table , self .sort_reverse )
6079
+
6080
+ def sort_cycle (self , column : str , table : str ) -> int :
6081
+ """
6082
+ Cycle between original sort order of the ResultSet, ASC by column, and DESC by
6083
+ column with each call.
6084
+
6085
+ :param column: The column name to cycle the sort on
6086
+ :param table: The table that the column belongs to
6087
+ :returns: A ResultSet sort constant; ResultSet.SORT_NONE, ResultSet.SORT_ASC, or
6088
+ ResultSet.SORT_DESC
6089
+ """
6090
+ if column != self .sort_column :
6091
+ self .sort_column = column
6092
+ self .sort_reverse = False
6093
+ self .sort (table )
6094
+ return ResultSet .SORT_ASC
6095
+ if not self .sort_reverse :
6096
+ self .sort_reverse = True
6097
+ self .sort (table )
6098
+ return ResultSet .SORT_DESC
6099
+ self .sort_reverse = False
6100
+ self .sort_column = None
6101
+ self .sort (table )
6102
+ return ResultSet .SORT_NONE
6103
+
6104
+
6105
+ class ResultSet2 :
5860
6106
5861
6107
"""
5862
6108
The ResultSet class is a generic result class so that working with the resultset of
@@ -6751,7 +6997,7 @@ def get_tables(self):
6751
6997
'WHERE type="table" AND name NOT like "sqlite%";'
6752
6998
)
6753
6999
cur = self .execute (q , silent = True )
6754
- return [ row [ "name" ] for row in cur ]
7000
+ return list ( cur [ "name" ])
6755
7001
6756
7002
def column_info (self , table ):
6757
7003
# Return a list of column names
@@ -6760,7 +7006,7 @@ def column_info(self, table):
6760
7006
names = []
6761
7007
col_info = ColumnInfo (self , table )
6762
7008
6763
- for row in rows :
7009
+ for index , row in rows . iterrows () :
6764
7010
name = row ["name" ]
6765
7011
names .append (name )
6766
7012
domain = row ["type" ]
@@ -6790,7 +7036,7 @@ def relationships(self):
6790
7036
f"PRAGMA foreign_key_list({ self .quote_table (from_table )} )" , silent = True
6791
7037
)
6792
7038
6793
- for row in rows :
7039
+ for index , row in rows . iterrows () :
6794
7040
dic = {}
6795
7041
# Add the relationship if it's in the requery list
6796
7042
if row ["on_update" ] == "CASCADE" :
0 commit comments