-
-
Notifications
You must be signed in to change notification settings - Fork 1k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[ENH] Selection of format and compression in save data widget #3147
Changes from 5 commits
3a43361
b2ed4af
cecb88a
2e4c184
6902f7b
35fd10a
8687b30
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,12 +1,28 @@ | ||
import os.path | ||
import pathlib | ||
|
||
from AnyQt.QtWidgets import QFormLayout | ||
from AnyQt.QtCore import Qt | ||
|
||
from Orange.data.table import Table | ||
from Orange.data.io import Compression, FileFormat | ||
from Orange.widgets import gui, widget | ||
from Orange.widgets.settings import Setting | ||
from Orange.data.io import FileFormat | ||
from Orange.widgets.utils import filedialogs | ||
from Orange.widgets.widget import Input | ||
|
||
FILE_TYPES = [ | ||
("Tab-delimited (.tab)", ".tab", False), | ||
("Comma-seperated values (.csv)", ".csv", False), | ||
("Pickle (.pkl)", ".pkl", True), | ||
] | ||
|
||
COMPRESSIONS = [ | ||
("gzip ({})".format(Compression.GZIP), Compression.GZIP), | ||
("bzip2 ({})".format(Compression.BZIP2), Compression.BZIP2), | ||
("lzma ({})".format(Compression.XZ), Compression.XZ), | ||
] | ||
|
||
|
||
class OWSave(widget.OWWidget): | ||
name = "Save Data" | ||
|
@@ -22,58 +38,126 @@ class Inputs: | |
resizing_enabled = False | ||
|
||
last_dir = Setting("") | ||
last_filter = Setting("") | ||
auto_save = Setting(False) | ||
filetype = Setting(FILE_TYPES[0][0]) | ||
compression = Setting(COMPRESSIONS[0][0]) | ||
compress = Setting(False) | ||
|
||
def get_writer_selected(self): | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You now use |
||
writer = FileFormat.get_reader(self.type_ext) | ||
try: | ||
writer.EXTENSIONS = [ | ||
writer.EXTENSIONS[writer.EXTENSIONS.index(self.type_ext + self.compress_ext)]] | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You still have the problem mentioned in #3147 (comment) Instead of the try/except, you should just check ext = self.type_ext + self.compress_ext
if ext not in writer.EXTENSIONS:
self.Error.not_supported_extension()
return None
writer.EXTENSIONS = [ext]
return writer BTW, just a minor suggestion, but a better name for |
||
return writer | ||
except ValueError: | ||
self.Error.not_supported_extension() | ||
return None | ||
|
||
@classmethod | ||
def get_writers(cls, sparse): | ||
return [f for f in FileFormat.formats | ||
if getattr(f, 'write_file', None) and getattr(f, "EXTENSIONS", None) | ||
and (not sparse or getattr(f, 'SUPPORT_SPARSE_DATA', False))] | ||
def remove_extensions(cls, filename): | ||
if not filename: | ||
return None | ||
for ext in pathlib.PurePosixPath(filename).suffixes: | ||
filename = filename.replace(ext, '') | ||
return filename | ||
|
||
class Error(widget.OWWidget.Error): | ||
not_supported_extension = widget.Msg("Selected extension is not supported.") | ||
|
||
def __init__(self): | ||
super().__init__() | ||
self.data = None | ||
self.filename = "" | ||
self.basename = "" | ||
self.type_ext = "" | ||
self.compress_ext = "" | ||
self.writer = None | ||
|
||
form = QFormLayout( | ||
labelAlignment=Qt.AlignLeft, | ||
formAlignment=Qt.AlignLeft, | ||
rowWrapPolicy=QFormLayout.WrapLongRows, | ||
verticalSpacing=10, | ||
) | ||
|
||
box = gui.vBox(self.controlArea, "Format") | ||
|
||
gui.comboBox( | ||
box, self, "filetype", | ||
callback=self._update_text, | ||
items=[item for item, _, _ in FILE_TYPES], | ||
sendSelectedValue=True, | ||
) | ||
form.addRow("File type", self.controls.filetype, ) | ||
|
||
gui.comboBox( | ||
box, self, "compression", | ||
callback=self._update_text, | ||
items=[item for item, _ in COMPRESSIONS], | ||
sendSelectedValue=True, | ||
) | ||
gui.checkBox( | ||
box, self, "compress", label="Use compression", | ||
callback=self._update_text, | ||
) | ||
|
||
form.addRow(self.controls.compress, self.controls.compression) | ||
|
||
box.layout().addLayout(form) | ||
|
||
self.save = gui.auto_commit( | ||
self.controlArea, self, "auto_save", "Save", box=False, | ||
commit=self.save_file, callback=self.adjust_label, | ||
disabled=True, addSpace=True) | ||
self.saveAs = gui.button( | ||
disabled=True, addSpace=True | ||
) | ||
self.save_as = gui.button( | ||
self.controlArea, self, "Save As...", | ||
callback=self.save_file_as, disabled=True) | ||
self.saveAs.setMinimumWidth(220) | ||
callback=self.save_file_as, disabled=True | ||
) | ||
self.save_as.setMinimumWidth(220) | ||
self.adjustSize() | ||
|
||
def adjust_label(self): | ||
if self.filename: | ||
filename = os.path.split(self.filename)[1] | ||
text = ["Save as '{}'", "Auto save as '{}'"][self.auto_save] | ||
self.save.button.setText(text.format(filename)) | ||
text = "Auto save as '{}'" if self.auto_save else "Save as '{}'" | ||
self.save.button.setText( | ||
text.format(self.basename + self.type_ext + self.compress_ext)) | ||
|
||
@Inputs.data | ||
def dataset(self, data): | ||
self.data = data | ||
self.save.setDisabled(data is None) | ||
self.saveAs.setDisabled(data is None) | ||
self.save_as.setDisabled(data is None) | ||
if data is not None: | ||
self.save_file() | ||
|
||
self.controls.filetype.clear() | ||
if self.data.is_sparse(): | ||
self.controls.filetype.insertItems(0, [item for item, _, supports_sparse in FILE_TYPES | ||
if supports_sparse]) | ||
else: | ||
self.controls.filetype.insertItems(0, [item for item, _, _ in FILE_TYPES]) | ||
|
||
def save_file_as(self): | ||
file_name = self.filename or \ | ||
os.path.join(self.last_dir or os.path.expanduser("~"), | ||
getattr(self.data, 'name', '')) | ||
filename, writer, filter = filedialogs.open_filename_dialog_save( | ||
file_name, self.last_filter, self.get_writers(self.data.is_sparse())) | ||
file_name = self.remove_extensions(self.filename) or os.path.join( | ||
self.last_dir or os.path.expanduser("~"), | ||
getattr(self.data, 'name', '')) | ||
self.update_extension() | ||
writer = self.get_writer_selected() | ||
if not writer: | ||
return | ||
|
||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. too many blank lines |
||
filename, writer, _ = filedialogs.open_filename_dialog_save( | ||
file_name, '', [writer], | ||
) | ||
if not filename: | ||
return | ||
|
||
self.filename = filename | ||
self.writer = writer | ||
self.unconditional_save_file() | ||
self.last_dir = os.path.split(self.filename)[0] | ||
self.last_filter = filter | ||
self.basename = os.path.basename(self.remove_extensions(filename)) | ||
self.unconditional_save_file() | ||
self.adjust_label() | ||
|
||
def save_file(self): | ||
|
@@ -83,16 +167,27 @@ def save_file(self): | |
self.save_file_as() | ||
else: | ||
try: | ||
self.writer.write(self.filename, self.data) | ||
except Exception as errValue: | ||
self.error(str(errValue)) | ||
self.writer.write(os.path.join(self.last_dir, | ||
self.basename + self.type_ext + self.compress_ext), | ||
self.data) | ||
except Exception as err_value: | ||
self.error(str(err_value)) | ||
else: | ||
self.error() | ||
|
||
def update_extension(self): | ||
self.type_ext = [ext for name, ext, _ in FILE_TYPES if name == self.filetype][0] | ||
self.compress_ext = dict(COMPRESSIONS)[self.compression] if self.compress else '' | ||
|
||
def _update_text(self): | ||
self.update_extension() | ||
self.adjust_label() | ||
|
||
|
||
if __name__ == "__main__": | ||
import sys | ||
from AnyQt.QtWidgets import QApplication | ||
|
||
a = QApplication(sys.argv) | ||
table = Table("iris") | ||
ow = OWSave() | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Again, these are hard coded constants, which could become incorrect after changes elsewhere.
How did you know where to write False and where True? If you checked the attribute SUPPORT_SPARSE_DATA in reader classes it is better to import and reference that here directly...
Maybe even construct the whole triple directly from the class:
Please test the above code and fix if necessary. It was written here as a suggestion without trying it out.
Also, I think the description for PickledReader could be changed to "Pickled Orange data".