def read_data_with_plugins( path: Union[str, Sequence[str]], plugin: Optional[str] = None, ) -> Tuple[Optional[List[LayerData]], Optional[HookImplementation]]: """Iterate reader hooks and return first non-None LayerData or None. This function returns as soon as the path has been read successfully, while catching any plugin exceptions, storing them for later retrieval, providing useful error messages, and re-looping until either a read operation was successful, or no valid readers were found. Exceptions will be caught and stored as PluginErrors (in plugins.exceptions.PLUGIN_ERRORS) Parameters ---------- path : str The path (file, directory, url) to open plugin : str, optional Name of a plugin to use. If provided, will force ``path`` to be read with the specified ``plugin``. If the requested plugin cannot read ``path``, a PluginCallError will be raised. Returns ------- LayerData : list of tuples, or None LayerData that can be passed to :func:`Viewer._add_layer_from_data() <napari.components.viewer_model.ViewerModel._add_layer_from_data>`. ``LayerData`` is a list tuples, where each tuple is one of ``(data,)``, ``(data, meta)``, or ``(data, meta, layer_type)`` . If no reader plugins were found (or they all failed), returns ``None`` Raises ------ PluginCallError If ``plugin`` is specified but raises an Exception while reading. """ hookimpl: Optional[HookImplementation] res = _npe2.read(path, plugin) if res is not None: _ld, hookimpl = res return [] if _is_null_layer_sentinel(_ld) else _ld, hookimpl hook_caller = plugin_manager.hook.napari_get_reader path = abspath_or_url(path) if not plugin and isinstance(path, (str, pathlib.Path)): extension = os.path.splitext(path)[-1] plugin = plugin_manager.get_reader_for_extension(extension) hookimpl = None if plugin: if plugin not in plugin_manager.plugins: names = {i.plugin_name for i in hook_caller.get_hookimpls()} raise ValueError( trans._( "There is no registered plugin named '{plugin}'.\nNames of plugins offering readers are: {names}", deferred=True, plugin=plugin, names=names, )) reader = hook_caller._call_plugin(plugin, path=path) if not callable(reader): raise ValueError( trans._( 'Plugin {plugin!r} does not support file {path}', deferred=True, plugin=plugin, path=path, )) hookimpl = hook_caller.get_plugin_implementation(plugin) layer_data = reader(path) # if the reader returns a "null layer" sentinel indicating an empty # file, return an empty list, otherwise return the result or None if _is_null_layer_sentinel(layer_data): return [], hookimpl return layer_data or None, hookimpl errors: List[PluginCallError] = [] skip_impls: List[HookImplementation] = [] layer_data = None while True: result = hook_caller.call_with_result_obj(path=path, _skip_impls=skip_impls) reader = result.result # will raise exceptions if any occurred if not reader: # we're all out of reader plugins break try: layer_data = reader(path) # try to read data if layer_data: hookimpl = result.implementation break except Exception as exc: # collect the error and log it, but don't raise it. err = PluginCallError(result.implementation, cause=exc) err.log(logger=logger) errors.append(err) # don't try this impl again skip_impls.append(result.implementation) if not layer_data: # if layer_data is empty, it means no plugin could read path # we just want to provide some useful feedback, which includes # whether or not paths were passed to plugins as a list. if isinstance(path, (tuple, list)): message = trans._( 'No plugin found capable of reading [{repr_path}, ...] as stack.', deferred=True, repr_path=path[0], ) else: message = trans._( 'No plugin found capable of reading {repr_path}.', deferred=True, repr_path=repr(path), ) # TODO: change to a warning notification in a later PR raise ValueError(message) if errors: names = {repr(e.plugin_name) for e in errors} err_msg = f"({len(errors)}) error{'s' if len(errors) > 1 else ''} " err_msg += f"occurred in plugins: {', '.join(names)}. " err_msg += 'See full error logs in "Plugins → Plugin Errors..."' logger.error(err_msg) # if the reader returns a "null layer" sentinel indicating an empty file, # return an empty list, otherwise return the result or None _data = [] if _is_null_layer_sentinel(layer_data) else layer_data or None return _data, hookimpl
def read_data_with_plugins( path: Union[str, Sequence[str]], plugin: Optional[str] = None, plugin_manager: PluginManager = napari_plugin_manager, ) -> List[LayerData]: """Iterate reader hooks and return first non-None LayerData or None. This function returns as soon as the path has been read successfully, while catching any plugin exceptions, storing them for later retrievial, providing useful error messages, and relooping until either layer data is returned, or no valid readers are found. Exceptions will be caught and stored as PluginErrors (in plugins.exceptions.PLUGIN_ERRORS) Parameters ---------- path : str The path (file, directory, url) to open plugin : str, optional Name of a plugin to use. If provided, will force ``path`` to be read with the specified ``plugin``. If the requested plugin cannot read ``path``, a PluginCallError will be raised. plugin_manager : plugins.PluginManager, optional Instance of a napari PluginManager. by default the main napari plugin_manager will be used. Returns ------- LayerData : list of tuples, or None LayerData that can be passed to :func:`Viewer._add_layer_from_data() <napari.components.viewer_model.ViewerModel._add_layer_from_data>`. ``LayerData`` is a list tuples, where each tuple is one of ``(data,)``, ``(data, meta)``, or ``(data, meta, layer_type)`` . If no reader plugins are (or they all error), returns ``None`` Raises ------ PluginCallError If ``plugin`` is specified but raises an Exception while reading. """ hook_caller = plugin_manager.hook.napari_get_reader if plugin: if plugin not in plugin_manager.plugins: names = {i.plugin_name for i in hook_caller.get_hookimpls()} raise ValueError( f"There is no registered plugin named '{plugin}'.\n" f"Names of plugins offering readers are: {names}" ) reader = hook_caller._call_plugin(plugin, path=path) if not callable(reader): raise ValueError(f'Plugin {plugin!r} does not support file {path}') return reader(path) or [] errors: List[PluginCallError] = [] path = abspath_or_url(path) skip_impls: List[HookImplementation] = [] layer_data = None while True: result = hook_caller.call_with_result_obj( path=path, _skip_impls=skip_impls ) reader = result.result # will raise exceptions if any occurred if not reader: # we're all out of reader plugins break try: layer_data = reader(path) # try to read data if layer_data: break except Exception as exc: # collect the error and log it, but don't raise it. err = PluginCallError(result.implementation, cause=exc) err.log(logger=logger) errors.append(err) # don't try this impl again skip_impls.append(result.implementation) if not layer_data: # if layer_data is empty, it means no plugin could read path # we just want to provide some useful feedback, which includes # whether or not paths were passed to plugins as a list. if isinstance(path, (tuple, list)): path_repr = f"[{path[0]}, ...] as stack" else: path_repr = repr(path) # TODO: change to a warning notification in a later PR raise ValueError(f'No plugin found capable of reading {path_repr}.') if errors: names = set([repr(e.plugin_name) for e in errors]) err_msg = f"({len(errors)}) error{'s' if len(errors) > 1 else ''} " err_msg += f"occurred in plugins: {', '.join(names)}. " err_msg += 'See full error logs in "Plugins → Plugin Errors..."' logger.error(err_msg) return layer_data or []
def read_data_with_plugins( path: Union[str, Sequence[str]], plugin: Optional[str] = None, plugin_manager=napari_plugin_manager, ) -> Optional[LayerData]: """Iterate reader hooks and return first non-None LayerData or None. This function returns as soon as the path has been read successfully, while catching any plugin exceptions, storing them for later retrievial, providing useful error messages, and relooping until either layer data is returned, or no valid readers are found. Exceptions will be caught and stored as PluginErrors (in plugins.exceptions.PLUGIN_ERRORS) Parameters ---------- path : str The path (file, directory, url) to open plugin : str, optional Name of a plugin to use. If provided, will force ``path`` to be read with the specified ``plugin``. If the requested plugin cannot read ``path``, a PluginCallError will be raised. plugin_manager : plugins.PluginManager, optional Instance of a napari PluginManager. by default the main napari plugin_manager will be used. Returns ------- LayerData : list of tuples, or None LayerData that can be passed to :func:`Viewer._add_layer_from_data() <napari.components.add_layers_mixin.AddLayersMixin._add_layer_from_data>`. ``LayerData`` is a list tuples, where each tuple is one of ``(data,)``, ``(data, meta)``, or ``(data, meta, layer_type)`` . If no reader plugins are (or they all error), returns ``None`` Raises ------ PluginCallError If ``plugin`` is specified but raises an Exception while reading. """ hook_caller = plugin_manager.hook.napari_get_reader if plugin: reader = hook_caller._call_plugin(plugin, path=path) return reader(path) path = abspath_or_url(path) skip_impls: List[HookImplementation] = [] while True: result = hook_caller.call_with_result_obj(path=path, _skip_impls=skip_impls) reader = result.result # will raise exceptions if any occured if not reader: # we're all out of reader plugins return None try: return reader(path) # try to read data except Exception as exc: err = PluginCallError(result.implementation, cause=exc) # don't try this impl again skip_impls.append(result.implementation) if result.implementation != 'builtins': # If builtins doesn't work, they will get a "no reader" found # error anyway, so it looks a bit weird to show them that the # "builtin plugin" didn't work. err.log(logger=logger)