1+ # -*- coding: utf-8 -*-
2+ """
3+ Created on Sun Nov 27 20:57:39 2016
4+
5+ @author: Zeke
6+ """
7+ import os
8+ import pandas as pd
9+ from qtpandas .models .DataFrameModel import DataFrameModel , read_file
10+ from collections import defaultdict
11+ import datetime
12+ from compat import QtCore
13+
14+
15+ class DataFrameModelManager (QtCore .QObject ):
16+ """
17+ A central storage unit for managing
18+ DataFrameModels.
19+ """
20+ signalNewModelRead = QtCore .Signal (str )
21+ signalModelDestroyed = QtCore .Signal (str )
22+
23+ def __init__ (self ):
24+ QtCore .QObject .__init__ (self )
25+ self ._models = {}
26+ self ._updates = defaultdict (list )
27+ self ._paths_read = []
28+ self ._paths_updated = []
29+
30+ @property
31+ def file_paths (self ):
32+ """Returns a list of the currently stored file paths"""
33+ return list (self ._models .keys ())
34+
35+ @property
36+ def models (self ):
37+ """Returns a list of all currently stored DataFrameModels"""
38+ return list (self ._models .values ())
39+
40+ @property
41+ def last_path_read (self ):
42+ """Returns the last path read (via the DataFrameModelManager.read_file method)"""
43+ if self ._paths_read :
44+ return self ._paths_read [- 1 ]
45+ else :
46+ return None
47+
48+ @property
49+ def last_path_updated (self ):
50+ """Returns the last path to register an update. (or None)"""
51+ if self ._paths_updated :
52+ return self ._paths_updated [- 1 ]
53+ else :
54+ return None
55+
56+ def save_file (self , filepath , save_as = None , keep_orig = False , ** kwargs ):
57+ """
58+ Saves a DataFrameModel to a file.
59+
60+ :param filepath: (str)
61+ The filepath of the DataFrameModel to save.
62+ :param save_as: (str, default None)
63+ The new filepath to save as.
64+ :param keep_orig: (bool, default False)
65+ True keeps the original filepath/DataFrameModel if save_as is specified.
66+ :param kwargs:
67+ pandas.DataFrame.to_excel(**kwargs) if .xlsx
68+ pandas.DataFrame.to_csv(**kwargs) otherwise.
69+ :return: None
70+ """
71+ df = self ._models [filepath ].dataFrame ()
72+ kwargs ['index' ] = kwargs .get ('index' , False )
73+
74+ if save_as is not None :
75+ to_path = save_as
76+ else :
77+ to_path = filepath
78+
79+ ext = os .path .splitext (to_path )[1 ].lower ()
80+
81+ if ext == ".xlsx" :
82+ kwargs .pop ('sep' , None )
83+ df .to_excel (to_path , ** kwargs )
84+
85+ elif ext in ['.csv' ,'.txt' ]:
86+ df .to_csv (to_path , ** kwargs )
87+
88+ else :
89+ raise NotImplementedError ("Cannot save file of type {}" .format (ext ))
90+
91+ if save_as is not None :
92+ if keep_orig is False :
93+ # Re-purpose the original model
94+ # Todo - capture the DataFrameModelManager._updates too
95+ model = self ._models .pop (filepath )
96+ model ._filePath = to_path
97+ else :
98+ # Create a new model.
99+ model = DataFrameModel ()
100+ model .setDataFrame (df , copyDataFrame = True , filePath = to_path )
101+
102+ self ._models [to_path ] = model
103+
104+ def set_model (self , df_model : DataFrameModel , file_path ):
105+ """
106+ Sets a DataFrameModel and registers it to the given file_path.
107+ :param df_model: (DataFrameModel)
108+ The DataFrameModel to register.
109+ :param file_path:
110+ The file path to associate with the DataFrameModel.
111+ *Overrides the current filePath on the DataFrameModel (if any)
112+ :return: None
113+ """
114+ assert isinstance (df_model , DataFrameModel ), "df_model argument must be a DataFrameModel!"
115+ df_model ._filePath = file_path
116+
117+ try :
118+ self ._models [file_path ]
119+ except KeyError :
120+ self .signalNewModelRead .emit (file_path )
121+
122+ self ._models [file_path ] = df_model
123+
124+ def get_model (self , filepath ):
125+ """
126+ Returns the DataFrameModel registered to filepath
127+ """
128+ return self ._models [filepath ]
129+
130+ def get_frame (self , filepath ):
131+ """Returns the DataFrameModel.dataFrame() registered to filepath """
132+ return self ._models [filepath ].dataFrame ()
133+
134+ def update_file (self , filepath , df , notes = None ):
135+ """
136+ Sets a new DataFrame for the DataFrameModel registered to filepath.
137+ :param filepath (str)
138+ The filepath to the DataFrameModel to be updated
139+ :param df (pandas.DataFrame)
140+ The new DataFrame to register to the model.
141+
142+ :param notes (str, default None)
143+ Optional notes to register along with the update.
144+
145+ """
146+ assert isinstance (df , pd .DataFrame ), "Cannot update file with type '{}'" .format (type (df ))
147+
148+ self ._models [filepath ].setDataFrame (df , copyDataFrame = False )
149+
150+ if notes :
151+ update = dict (date = pd .Timestamp (datetime .datetime .now ()),
152+ notes = notes )
153+
154+ self ._updates [filepath ].append (update )
155+ self ._paths_updated .append (filepath )
156+
157+ def remove_file (self , filepath ):
158+ """
159+ Removes the DataFrameModel from being registered.
160+ :param filepath: (str)
161+ The filepath to delete from the DataFrameModelManager.
162+ :return: None
163+ """
164+ self ._models .pop (filepath )
165+ self ._updates .pop (filepath , default = None )
166+ self .signalModelDestroyed .emit (filepath )
167+
168+ def read_file (self , filepath , ** kwargs ) -> DataFrameModel :
169+ """
170+ Reads a filepath into a DataFrameModel and registers
171+ it.
172+ Example use:
173+ dfmm = DataFrameModelManger()
174+ dfmm.read_file(path_to_file)
175+ dfm = dfmm.get_model(path_to_file)
176+ df = dfm.get_frame(path_to_file)
177+
178+ :param filepath: (str)
179+ The filepath to read
180+ :param kwargs:
181+ .xlsx files: pandas.read_excel(**kwargs)
182+ .csv files: pandas.read_csv(**kwargs)
183+ :return: DataFrameModel
184+ """
185+ try :
186+ model = self ._models [filepath ]
187+ except KeyError :
188+ model = read_file (filepath , ** kwargs )
189+ self ._models [filepath ] = model
190+ self .signalNewModelRead .emit (filepath )
191+ finally :
192+ self ._paths_read .append (filepath )
193+
194+ return self ._models [filepath ]
195+
0 commit comments