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

Last change on this file since d1520fc 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
RevLine 
[4749514]1"""
2    File handler to support different file extensions.
[270c882b]3    Uses reflectometer registry utility.
[f06d7fc]4
[4749514]5    The default readers are found in the 'readers' sub-module
6    and registered by default at initialization time.
[f06d7fc]7
[4749514]8    To add a new default reader, one must register it in
9    the register_readers method found in readers/__init__.py.
[f06d7fc]10
[4749514]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#####################################################################
[5d8f9b3]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
[4749514]21######################################################################
22
23import os
24import sys
25import logging
26import time
27from zipfile import ZipFile
[574adc7]28
[b699768]29from sas.sascalc.data_util.registry import ExtensionRegistry
[574adc7]30
[4749514]31# Default readers are defined in the readers sub-module
[574adc7]32from . import readers
33from .loader_exceptions import NoKnownLoaderException, FileContentsException,\
[da8bb53]34    DefaultReaderException
[574adc7]35from .readers import ascii_reader
36from .readers import cansas_reader
37from .readers import cansas_reader_HDF5
[4749514]38
[463e7ffc]39logger = logging.getLogger(__name__)
[c155a16]40
[5d8f9b3]41
[4749514]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__()
[f06d7fc]49
[270c882b]50        # Writers
[4749514]51        self.writers = {}
[f06d7fc]52
[270c882b]53        # List of wildcards
[4749514]54        self.wildcards = ['All (*.*)|*.*']
[f06d7fc]55
[ef63dd9]56        # Deprecated extensions
[9592c6f]57        self.deprecated_extensions = ['.asc', '.nxs']
[ef63dd9]58
[270c882b]59        # Creation time, for testing
[4749514]60        self._created = time.time()
[f06d7fc]61
[4749514]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
[b9b612a]73        Defaults to the ascii (multi-column), cansas XML, and cansas NeXuS
74        readers if no reader was registered for the file's extension.
[4749514]75        """
[dcb91cf]76        # Gets set to a string if the file has an associated reader that fails
77        msg_from_reader = None
[9592c6f]78        extlist = [ext for ext in self.deprecated_extensions if
79                   path.lower().endswith(ext)]
[ef63dd9]80        if extlist:
[9592c6f]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."
[ef63dd9]86                   .format(path))
87            raise RuntimeError(msg)
[4749514]88        try:
89            return super(Registry, self).load(path, format=format)
[dc8d1c2]90        #except Exception: raise  # for debugging, don't use fallback loader
[371b9e2]91        except NoKnownLoaderException as nkl_e:
[dcb91cf]92            pass  # Try the ASCII reader
[3ece5dd]93        except FileContentsException as fc_exc:
[dcb91cf]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
[3ece5dd]97        except Exception:
[371b9e2]98            pass
[3eb7bf2]99
[dcb91cf]100        # File has no associated reader, or the associated reader failed.
101        # Try the ASCII reader
[b9b612a]102        try:
103            ascii_loader = ascii_reader.Reader()
104            return ascii_loader.read(path)
[da8bb53]105        except DefaultReaderException:
[3ece5dd]106            pass  # Loader specific error to try the cansas XML reader
107        except FileContentsException as e:
[dcb91cf]108            if msg_from_reader is None:
109                raise RuntimeError(e.message)
[3eb7bf2]110
111        # ASCII reader failed - try CanSAS xML reader
[b9b612a]112        try:
113            cansas_loader = cansas_reader.Reader()
114            return cansas_loader.read(path)
[da8bb53]115        except DefaultReaderException:
[3eb7bf2]116            pass  # Loader specific error to try the NXcanSAS reader
[3ece5dd]117        except FileContentsException as e:
[dcb91cf]118            if msg_from_reader is None:
119                raise RuntimeError(e.message)
120        except Exception:
[da8bb53]121            pass
[3eb7bf2]122
123        # CanSAS XML reader failed - try NXcanSAS reader
[b9b612a]124        try:
125            cansas_nexus_loader = cansas_reader_HDF5.Reader()
126            return cansas_nexus_loader.read(path)
[8dec7e7]127        except DefaultReaderException as e:
128            logging.error("No default loader can load the data")
[b9b612a]129            # No known reader available. Give up and throw an error
[dcb91cf]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)
[8dec7e7]138        except FileContentsException as e:
[dcb91cf]139            err_msg = msg_from_reader if msg_from_reader is not None else e.message
140            raise RuntimeError(err_msg)
[f06d7fc]141
[4749514]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.
[f06d7fc]147
[4749514]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)
[f06d7fc]159
[4749514]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
[c155a16]165            logger.warning(msg)
[4749514]166            return readers_found
[f06d7fc]167
[4749514]168        for item in os.listdir(dir):
169            full_path = os.path.join(dir, item)
170            if os.path.isfile(full_path):
[f06d7fc]171
[4749514]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)
[c155a16]183                        logger.error(msg)
[f06d7fc]184
[4749514]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()
[f06d7fc]191
[4749514]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(),
[f06d7fc]199                                                    locals(), [""])
[4749514]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)
[c155a16]205                                logger.error(msg)
[f06d7fc]206
[4749514]207                    except:
208                        msg = "Loader: Error importing "
209                        msg += " %s\n  %s" % (item, sys.exc_value)
[c155a16]210                        logger.error(msg)
[f06d7fc]211
[4749514]212        return readers_found
[f06d7fc]213
[4749514]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
[f06d7fc]219
[4749514]220        :param ext: file extension [string]
221        :param module: module object
222        """
223        reader_found = False
[f06d7fc]224
[4749514]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
[f06d7fc]235
[4749514]236                # Keep track of wildcards
237                type_name = module.__name__
238                if hasattr(loader, 'type_name'):
239                    type_name = loader.type_name
[f06d7fc]240
[4749514]241                wcard = "%s files (*%s)|*%s" % (type_name, ext.lower(),
[f06d7fc]242                                                ext.lower())
[4749514]243                if wcard not in self.wildcards:
244                    self.wildcards.append(wcard)
[f06d7fc]245
[4749514]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)
[f06d7fc]252
[4749514]253            except:
254                msg = "Loader: Error accessing"
255                msg += " Reader in %s\n  %s" % (module.__name__, sys.exc_value)
[c155a16]256                logger.error(msg)
[4749514]257        return reader_found
258
259    def associate_file_reader(self, ext, loader):
260        """
261        Append a reader object to readers
[f06d7fc]262
[4749514]263        :param ext: file extension [string]
264        :param module: reader object
265        """
266        reader_found = False
[f06d7fc]267
[4749514]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
[f06d7fc]276
[4749514]277            # Keep track of wildcards
278            if hasattr(loader, 'type_name'):
279                type_name = loader.type_name
[f06d7fc]280
[4749514]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)
[f06d7fc]285
[4749514]286        except:
287            msg = "Loader: Error accessing Reader "
288            msg += "in %s\n  %s" % (loader.__name__, sys.exc_value)
[c155a16]289            logger.error(msg)
[4749514]290        return reader_found
291
292    def _identify_plugin(self, module):
293        """
[f06d7fc]294        Look into a module to find whether it contains a
[4749514]295        Reader class. If so, add it to readers and (potentially)
296        to the list of writers.
297        :param module: module object
[f06d7fc]298
[4749514]299        """
300        reader_found = False
[f06d7fc]301
[4749514]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
[f06d7fc]314
[4749514]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(),
[f06d7fc]320                                                    ext.lower())
[4749514]321                    if wcard not in self.wildcards:
322                        self.wildcards.append(wcard)
[f06d7fc]323
[4749514]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)
[f06d7fc]330
[4749514]331            except:
332                msg = "Loader: Error accessing Reader"
333                msg += " in %s\n  %s" % (module.__name__, sys.exc_value)
[c155a16]334                logger.error(msg)
[4749514]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
[dc8d1c2]345        extlist.sort(key=len)
[4749514]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:
[574adc7]359            raise ValueError("Unknown file type for " + path)
[4749514]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)
[574adc7]378            except Exception:
[4749514]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
[f06d7fc]383
[4749514]384class Loader(object):
385    """
386    Utility class to use the Registry as a singleton.
387    """
388    ## Registry instance
389    __registry = Registry()
[f06d7fc]390
[4749514]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
[f06d7fc]396
[4749514]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
[f06d7fc]405
[4749514]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
[f06d7fc]414
[4749514]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)
[f06d7fc]420
[4749514]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
[f06d7fc]426        :param format: format to write the data in
[4749514]427        """
428        return self.__registry.save(file, data, format)
[f06d7fc]429
[4749514]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
[f06d7fc]436
437    def find_plugins(self, directory):
[4749514]438        """
439        Find plugins in a given directory
[f06d7fc]440
[4749514]441        :param dir: directory to look into to find new readers/writers
442        """
[f06d7fc]443        return self.__registry.find_plugins(directory)
444
[4749514]445    def get_wildcards(self):
[f06d7fc]446        """
447        Return the list of wildcards
448        """
[4749514]449        return self.__registry.wildcards
Note: See TracBrowser for help on using the repository browser.