source: sasview/src/sas/sascalc/dataloader/loader.py @ 9592c6f

Last change on this file since 9592c6f was 9592c6f, checked in by krzywon, 7 years ago

More explicit error message when the loader sees a deprecated data set.

  • Property mode set to 100644
File size: 16.1 KB
Line 
1"""
2    File handler to support different file extensions.
3    Uses reflectometer registry utility.
4
5    The default readers are found in the 'readers' sub-module
6    and registered by default at initialization time.
7
8    To add a new default reader, one must register it in
9    the register_readers method found in readers/__init__.py.
10
11    A utility method (find_plugins) is available to inspect
12    a directory (for instance, a user plug-in directory) and
13    look for new readers/writers.
14"""
15#####################################################################
16# This software was developed by the University of Tennessee as part of the
17# Distributed Data Analysis of Neutron Scattering Experiments (DANSE)
18# project funded by the US National Science Foundation.
19# See the license text in license.txt
20# copyright 2008, University of Tennessee
21######################################################################
22
23import os
24import sys
25import logging
26import time
27from zipfile import ZipFile
28
29from sas.sascalc.data_util.registry import ExtensionRegistry
30
31# Default readers are defined in the readers sub-module
32from . import readers
33from .loader_exceptions import NoKnownLoaderException, FileContentsException,\
34    DefaultReaderException
35from .readers import ascii_reader
36from .readers import cansas_reader
37from .readers import cansas_reader_HDF5
38
39logger = logging.getLogger(__name__)
40
41
42class Registry(ExtensionRegistry):
43    """
44    Registry class for file format extensions.
45    Readers and writers are supported.
46    """
47    def __init__(self):
48        super(Registry, self).__init__()
49
50        # Writers
51        self.writers = {}
52
53        # List of wildcards
54        self.wildcards = ['All (*.*)|*.*']
55
56        # Deprecated extensions
57        self.deprecated_extensions = ['.asc', '.nxs']
58
59        # Creation time, for testing
60        self._created = time.time()
61
62        # Register default readers
63        readers.read_associations(self)
64
65    def load(self, path, format=None):
66        """
67        Call the loader for the file type of path.
68
69        :param path: file path
70        :param format: explicit extension, to force the use
71            of a particular reader
72
73        Defaults to the ascii (multi-column), cansas XML, and cansas NeXuS
74        readers if no reader was registered for the file's extension.
75        """
76        # Gets set to a string if the file has an associated reader that fails
77        msg_from_reader = None
78        extlist = [ext for ext in self.deprecated_extensions if
79                   path.lower().endswith(ext)]
80        if extlist:
81            msg = ("\rThe file you are attempting to load, {}, is no longer "
82                   "loadable by SasView. The file extension suggests the data "
83                   "set is in pixel space. SasView will no longer reduce raw da"
84                   "ta sets. Please provide fully reduced data in Q space. No a"
85                   "ttempt to load the data has been made."
86                   .format(path))
87            raise RuntimeError(msg)
88        try:
89            return super(Registry, self).load(path, format=format)
90        #except Exception: raise  # for debugging, don't use fallback loader
91        except NoKnownLoaderException as nkl_e:
92            pass  # Try the ASCII reader
93        except FileContentsException as fc_exc:
94            # File has an associated reader but it failed.
95            # Save the error message to display later, but try the 3 default loaders
96            msg_from_reader = fc_exc.message
97        except Exception:
98            pass
99
100        # File has no associated reader, or the associated reader failed.
101        # Try the ASCII reader
102        try:
103            ascii_loader = ascii_reader.Reader()
104            return ascii_loader.read(path)
105        except DefaultReaderException:
106            pass  # Loader specific error to try the cansas XML reader
107        except FileContentsException as e:
108            if msg_from_reader is None:
109                raise RuntimeError(e.message)
110
111        # ASCII reader failed - try CanSAS xML reader
112        try:
113            cansas_loader = cansas_reader.Reader()
114            return cansas_loader.read(path)
115        except DefaultReaderException:
116            pass  # Loader specific error to try the NXcanSAS reader
117        except FileContentsException as e:
118            if msg_from_reader is None:
119                raise RuntimeError(e.message)
120        except Exception:
121            pass
122
123        # CanSAS XML reader failed - try NXcanSAS reader
124        try:
125            cansas_nexus_loader = cansas_reader_HDF5.Reader()
126            return cansas_nexus_loader.read(path)
127        except DefaultReaderException as e:
128            logging.error("No default loader can load the data")
129            # No known reader available. Give up and throw an error
130            if msg_from_reader is None:
131                msg = "\nUnknown data format: {}.\nThe file is not a ".format(path)
132                msg += "known format that can be loaded by SasView.\n"
133                raise NoKnownLoaderException(msg)
134            else:
135                # Associated reader and default readers all failed.
136                # Show error message from associated reader
137                raise RuntimeError(msg_from_reader)
138        except FileContentsException as e:
139            err_msg = msg_from_reader if msg_from_reader is not None else e.message
140            raise RuntimeError(err_msg)
141
142    def find_plugins(self, dir):
143        """
144        Find readers in a given directory. This method
145        can be used to inspect user plug-in directories to
146        find new readers/writers.
147
148        :param dir: directory to search into
149        :return: number of readers found
150        """
151        readers_found = 0
152        temp_path = os.path.abspath(dir)
153        if not os.path.isdir(temp_path):
154            temp_path = os.path.join(os.getcwd(), dir)
155        if not os.path.isdir(temp_path):
156            temp_path = os.path.join(os.path.dirname(__file__), dir)
157        if not os.path.isdir(temp_path):
158            temp_path = os.path.join(os.path.dirname(sys.path[0]), dir)
159
160        dir = temp_path
161        # Check whether the directory exists
162        if not os.path.isdir(dir):
163            msg = "DataLoader couldn't locate DataLoader plugin folder."
164            msg += """ "%s" does not exist""" % dir
165            logger.warning(msg)
166            return readers_found
167
168        for item in os.listdir(dir):
169            full_path = os.path.join(dir, item)
170            if os.path.isfile(full_path):
171
172                # Process python files
173                if item.endswith('.py'):
174                    toks = os.path.splitext(os.path.basename(item))
175                    try:
176                        sys.path.insert(0, os.path.abspath(dir))
177                        module = __import__(toks[0], globals(), locals())
178                        if self._identify_plugin(module):
179                            readers_found += 1
180                    except:
181                        msg = "Loader: Error importing "
182                        msg += "%s\n  %s" % (item, sys.exc_value)
183                        logger.error(msg)
184
185                # Process zip files
186                elif item.endswith('.zip'):
187                    try:
188                        # Find the modules in the zip file
189                        zfile = ZipFile(item)
190                        nlist = zfile.namelist()
191
192                        sys.path.insert(0, item)
193                        for mfile in nlist:
194                            try:
195                                # Change OS path to python path
196                                fullname = mfile.replace('/', '.')
197                                fullname = os.path.splitext(fullname)[0]
198                                module = __import__(fullname, globals(),
199                                                    locals(), [""])
200                                if self._identify_plugin(module):
201                                    readers_found += 1
202                            except:
203                                msg = "Loader: Error importing"
204                                msg += " %s\n  %s" % (mfile, sys.exc_value)
205                                logger.error(msg)
206
207                    except:
208                        msg = "Loader: Error importing "
209                        msg += " %s\n  %s" % (item, sys.exc_value)
210                        logger.error(msg)
211
212        return readers_found
213
214    def associate_file_type(self, ext, module):
215        """
216        Look into a module to find whether it contains a
217        Reader class. If so, APPEND it to readers and (potentially)
218        to the list of writers for the given extension
219
220        :param ext: file extension [string]
221        :param module: module object
222        """
223        reader_found = False
224
225        if hasattr(module, "Reader"):
226            try:
227                # Find supported extensions
228                loader = module.Reader()
229                if ext not in self.loaders:
230                    self.loaders[ext] = []
231                # Append the new reader to the list
232                self.loaders[ext].append(loader.read)
233
234                reader_found = True
235
236                # Keep track of wildcards
237                type_name = module.__name__
238                if hasattr(loader, 'type_name'):
239                    type_name = loader.type_name
240
241                wcard = "%s files (*%s)|*%s" % (type_name, ext.lower(),
242                                                ext.lower())
243                if wcard not in self.wildcards:
244                    self.wildcards.append(wcard)
245
246                # Check whether writing is supported
247                if hasattr(loader, 'write'):
248                    if ext not in self.writers:
249                        self.writers[ext] = []
250                    # Append the new writer to the list
251                    self.writers[ext].append(loader.write)
252
253            except:
254                msg = "Loader: Error accessing"
255                msg += " Reader in %s\n  %s" % (module.__name__, sys.exc_value)
256                logger.error(msg)
257        return reader_found
258
259    def associate_file_reader(self, ext, loader):
260        """
261        Append a reader object to readers
262
263        :param ext: file extension [string]
264        :param module: reader object
265        """
266        reader_found = False
267
268        try:
269            # Find supported extensions
270            if ext not in self.loaders:
271                self.loaders[ext] = []
272            # Append the new reader to the list
273            self.loaders[ext].append(loader.read)
274
275            reader_found = True
276
277            # Keep track of wildcards
278            if hasattr(loader, 'type_name'):
279                type_name = loader.type_name
280
281                wcard = "%s files (*%s)|*%s" % (type_name, ext.lower(),
282                                                ext.lower())
283                if wcard not in self.wildcards:
284                    self.wildcards.append(wcard)
285
286        except:
287            msg = "Loader: Error accessing Reader "
288            msg += "in %s\n  %s" % (loader.__name__, sys.exc_value)
289            logger.error(msg)
290        return reader_found
291
292    def _identify_plugin(self, module):
293        """
294        Look into a module to find whether it contains a
295        Reader class. If so, add it to readers and (potentially)
296        to the list of writers.
297        :param module: module object
298
299        """
300        reader_found = False
301
302        if hasattr(module, "Reader"):
303            try:
304                # Find supported extensions
305                loader = module.Reader()
306                for ext in loader.ext:
307                    if ext not in self.loaders:
308                        self.loaders[ext] = []
309                    # When finding a reader at run time,
310                    # treat this reader as the new default
311                    self.loaders[ext].insert(0, loader.read)
312
313                    reader_found = True
314
315                    # Keep track of wildcards
316                    type_name = module.__name__
317                    if hasattr(loader, 'type_name'):
318                        type_name = loader.type_name
319                    wcard = "%s files (*%s)|*%s" % (type_name, ext.lower(),
320                                                    ext.lower())
321                    if wcard not in self.wildcards:
322                        self.wildcards.append(wcard)
323
324                # Check whether writing is supported
325                if hasattr(loader, 'write'):
326                    for ext in loader.ext:
327                        if ext not in self.writers:
328                            self.writers[ext] = []
329                        self.writers[ext].insert(0, loader.write)
330
331            except:
332                msg = "Loader: Error accessing Reader"
333                msg += " in %s\n  %s" % (module.__name__, sys.exc_value)
334                logger.error(msg)
335        return reader_found
336
337    def lookup_writers(self, path):
338        """
339        :return: the loader associated with the file type of path.
340        :Raises ValueError: if file type is not known.
341        """
342        # Find matching extensions
343        extlist = [ext for ext in self.extensions() if path.endswith(ext)]
344        # Sort matching extensions by decreasing order of length
345        extlist.sort(key=len)
346        # Combine loaders for matching extensions into one big list
347        writers = []
348        for L in [self.writers[ext] for ext in extlist]:
349            writers.extend(L)
350        # Remove duplicates if they exist
351        if len(writers) != len(set(writers)):
352            result = []
353            for L in writers:
354                if L not in result:
355                    result.append(L)
356            writers = L
357        # Raise an error if there are no matching extensions
358        if len(writers) == 0:
359            raise ValueError("Unknown file type for " + path)
360        # All done
361        return writers
362
363    def save(self, path, data, format=None):
364        """
365        Call the writer for the file type of path.
366
367        Raises ValueError if no writer is available.
368        Raises KeyError if format is not available.
369        May raise a writer-defined exception if writer fails.
370        """
371        if format is None:
372            writers = self.lookup_writers(path)
373        else:
374            writers = self.writers[format]
375        for fn in writers:
376            try:
377                return fn(path, data)
378            except Exception:
379                pass  # give other loaders a chance to succeed
380        # If we get here it is because all loaders failed
381        raise  # reraises last exception
382
383
384class Loader(object):
385    """
386    Utility class to use the Registry as a singleton.
387    """
388    ## Registry instance
389    __registry = Registry()
390
391    def associate_file_type(self, ext, module):
392        """
393        Look into a module to find whether it contains a
394        Reader class. If so, append it to readers and (potentially)
395        to the list of writers for the given extension
396
397        :param ext: file extension [string]
398        :param module: module object
399        """
400        return self.__registry.associate_file_type(ext, module)
401
402    def associate_file_reader(self, ext, loader):
403        """
404        Append a reader object to readers
405
406        :param ext: file extension [string]
407        :param module: reader object
408        """
409        return self.__registry.associate_file_reader(ext, loader)
410
411    def load(self, file, format=None):
412        """
413        Load a file
414
415        :param file: file name (path)
416        :param format: specified format to use (optional)
417        :return: DataInfo object
418        """
419        return self.__registry.load(file, format)
420
421    def save(self, file, data, format):
422        """
423        Save a DataInfo object to file
424        :param file: file name (path)
425        :param data: DataInfo object
426        :param format: format to write the data in
427        """
428        return self.__registry.save(file, data, format)
429
430    def _get_registry_creation_time(self):
431        """
432        Internal method used to test the uniqueness
433        of the registry object
434        """
435        return self.__registry._created
436
437    def find_plugins(self, directory):
438        """
439        Find plugins in a given directory
440
441        :param dir: directory to look into to find new readers/writers
442        """
443        return self.__registry.find_plugins(directory)
444
445    def get_wildcards(self):
446        """
447        Return the list of wildcards
448        """
449        return self.__registry.wildcards
Note: See TracBrowser for help on using the repository browser.