Skip to content

Commit d938e93

Browse files
committed
owfile: support readers with repeated extensions
FileFormat uses PRIORITY for preference. When formats share extension, use the format with the lowest priority.
1 parent 702bc1e commit d938e93

File tree

5 files changed

+64
-9
lines changed

5 files changed

+64
-9
lines changed

Orange/data/io.py

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -294,11 +294,18 @@ def _ext_to_attr_if_attr2(cls, attr, attr2):
294294
"""
295295
Return ``{ext: `attr`, ...}`` dict if ``cls`` has `attr2`.
296296
If `attr` is '', return ``{ext: cls, ...}`` instead.
297+
298+
If there are multiple formats for an extension, return a format
299+
with the lowest priority.
297300
"""
298-
return OrderedDict((ext, getattr(cls, attr, cls))
299-
for cls in cls.registry.values()
300-
if hasattr(cls, attr2)
301-
for ext in getattr(cls, 'EXTENSIONS', []))
301+
formats = OrderedDict()
302+
for format in sorted(cls.registry.values(), key=lambda x: x.PRIORITY):
303+
if not hasattr(format, attr2):
304+
continue
305+
for ext in getattr(format, 'EXTENSIONS', []):
306+
# Only adds if not yet registered
307+
formats.setdefault(ext, getattr(format, attr, format))
308+
return formats
302309

303310
@property
304311
def names(cls):
@@ -344,7 +351,9 @@ def write_file(cls, filename, data):
344351
iterable (list (rows) of lists of values (cols)).
345352
"""
346353

347-
PRIORITY = 10000 # Sort order in OWSave widget combo box, lower is better
354+
# Priority when multiple formats support the same extension. Also
355+
# the sort order in file open/save combo boxes. Lower is better.
356+
PRIORITY = 10000
348357

349358
def __init__(self, filename):
350359
"""

Orange/tests/test_io.py

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
from Orange.data.io import FileFormat, TabReader, CSVReader, PickleReader
1010
from Orange.data.table import get_sample_datasets_dir
1111

12+
1213
class WildcardReader(FileFormat):
1314
EXTENSIONS = ('.wild', '.wild[0-9]')
1415
DESCRIPTION = "Dummy reader for testing extensions"
@@ -35,6 +36,30 @@ def test_wildcard_extension(self):
3536
FileFormat.get_reader("t.wild2a")
3637

3738

39+
class SameExtension(FileFormat):
40+
PRIORITY = 100
41+
EXTENSIONS = ('.same_extension',)
42+
DESCRIPTION = "Same extension, different priority"
43+
44+
def read(self):
45+
pass
46+
47+
48+
class SameExtensionPreferred(SameExtension):
49+
PRIORITY = 90
50+
51+
52+
class SameExtensionL(SameExtension):
53+
PRIORITY = 110
54+
55+
56+
class TestMultipleSameExtension(unittest.TestCase):
57+
58+
def test_find_reader(self):
59+
reader = FileFormat.get_reader("some.same_extension")
60+
self.assertIsInstance(reader, SameExtensionPreferred)
61+
62+
3863
class TestLocate(unittest.TestCase):
3964

4065
def test_locate_sample_datasets(self):
@@ -49,7 +74,6 @@ def test_locate_sample_datasets(self):
4974
search_dirs=[get_sample_datasets_dir()])
5075
self.assertEqual(os.path.basename(iris), "iris.tab")
5176

52-
5377
def test_locate_wildcard_extension(self):
5478
tempdir = tempfile.mkdtemp()
5579
with self.assertRaises(OSError):

Orange/widgets/data/owfile.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -269,7 +269,9 @@ def browse_file(self, in_demos=False):
269269
else:
270270
start_file = self.last_path() or os.path.expanduser("~/")
271271

272-
filename, reader, _ = open_filename_dialog(start_file, None, FileFormat.readers)
272+
readers = [f for f in FileFormat.formats
273+
if getattr(f, 'read', None) and getattr(f, "EXTENSIONS", None)]
274+
filename, reader, _ = open_filename_dialog(start_file, None, readers)
273275
if not filename:
274276
return
275277
self.add_path(filename)

Orange/widgets/data/tests/test_owfile.py

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,15 @@ def read(self):
5454
return Orange.data.Table("iris")
5555

5656

57+
class MyCustomTabReader(FileFormat):
58+
EXTENSIONS = ('.tab',)
59+
DESCRIPTION = "Always return iris"
60+
PRIORITY = 999999
61+
62+
def read(self):
63+
return Orange.data.Table("iris")
64+
65+
5766
class TestOWFile(WidgetTest):
5867
# Attribute used to store event data so it does not get garbage
5968
# collected before event is processed.
@@ -230,6 +239,17 @@ def test_check_datetime_disabled(self):
230239
vartype_delegate.setEditorData(combo, idx(i))
231240
self.assertEqual(combo.count(), counts[i])
232241

242+
def test_reader_custom_tab(self):
243+
with named_file("", suffix=".tab") as fn:
244+
qname = MyCustomTabReader.qualified_name()
245+
reader = RecentPath(fn, None, None, file_format=qname)
246+
self.widget = self.create_widget(OWFile,
247+
stored_settings={"recent_paths": [reader]})
248+
self.widget.load_data()
249+
self.assertFalse(self.widget.Error.missing_reader.is_shown())
250+
outdata = self.get_output(self.widget.Outputs.data)
251+
self.assertEqual(len(outdata), 150) # loaded iris
252+
233253
def test_no_reader_extension(self):
234254
with named_file("", suffix=".xyz_unknown") as fn:
235255
no_reader = RecentPath(fn, None, None)

Orange/widgets/utils/filedialogs.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -115,13 +115,13 @@ def open_filename_dialog(start_dir, start_filter, file_formats, title="Open...",
115115
Args:
116116
start_dir (str): initial directory, optionally including the filename
117117
start_filter (str): initial filter
118-
file_formats (dict {extension: Orange.data.io.FileFormat}): file formats
118+
file_formats (a list of Orange.data.io.FileFormat): file formats
119119
title (str): title of the dialog
120120
dialog: a function that creates a QT dialog
121121
Returns:
122122
(filename, file_format, filter), or `(None, None, None)` on cancel
123123
"""
124-
file_formats = sorted(set(file_formats.values()), key=lambda w: w.PRIORITY)
124+
file_formats = sorted(set(file_formats), key=lambda w: (w.PRIORITY, w.DESCRIPTION))
125125
filters = [format_filter(f) for f in file_formats]
126126

127127
# add all readable files option

0 commit comments

Comments
 (0)