Skip to content

Commit

Permalink
Merge pull request #22 from bioio-devs/feature/auto-retrieve-plugins
Browse files Browse the repository at this point in the history
Auto retrieve plugins + adjust caching
  • Loading branch information
SeanLeRoy authored Nov 9, 2023
2 parents aae123e + 4ca121f commit 0bb4107
Show file tree
Hide file tree
Showing 2 changed files with 51 additions and 59 deletions.
14 changes: 12 additions & 2 deletions bioio/bio_image.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
from ome_types import OME

from .ome_utils import generate_ome_channel_id
from .plugins import PluginEntry, plugins_by_ext
from .plugins import PluginEntry, get_plugins

###############################################################################

Expand Down Expand Up @@ -48,6 +48,11 @@ class BioImage(biob.image_container.ImageContainer):
`M` dimension for tiles.
If image is not a mosaic, data won't be stitched or have an `M` dimension for
tiles.
use_plugin_cache: bool default False
Boolean for setting whether to use a plugin of the installed caches rather than
checking for installed plugins on each `BioImage` instance init.
If True, will use the cache of installed plugins discovered last `BioImage`
init.
fs_kwargs: Dict[str, Any]
Any specific keyword arguments to pass down to the fsspec created filesystem.
Default: {}
Expand Down Expand Up @@ -124,6 +129,7 @@ class BioImage(biob.image_container.ImageContainer):
def determine_plugin(
image: biob.types.ImageLike,
fs_kwargs: Dict[str, Any] = {},
use_plugin_cache: bool = False,
**kwargs: Any,
) -> PluginEntry:
"""
Expand All @@ -140,6 +146,9 @@ def determine_plugin(
exceptions.UnsupportedFileFormatError
No reader could be found that supports the provided image.
"""
# Fetch updated mapping of plugins
plugins_by_ext = get_plugins(use_cache=use_plugin_cache)

# Try reader detection based off of file path extension
image_str = str(type(image))
if isinstance(image, (str, Path)):
Expand Down Expand Up @@ -185,13 +194,14 @@ def __init__(
image: biob.types.ImageLike,
reader: Optional[Type[biob.reader.Reader]] = None,
reconstruct_mosaic: bool = True,
use_plugin_cache: bool = False,
fs_kwargs: Dict[str, Any] = {},
**kwargs: Any,
):
if reader is None:
# Determine reader class and create dask delayed array
self._plugin = BioImage.determine_plugin(
image, fs_kwargs=fs_kwargs, **kwargs
image, fs_kwargs=fs_kwargs, use_plugin_cache=use_plugin_cache, **kwargs
)
ReaderClass = self._plugin.metadata.get_reader()
else:
Expand Down
96 changes: 39 additions & 57 deletions bioio/plugins.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@

from typing import Dict, List, NamedTuple, Optional, Tuple

from bioio_base.reader import Reader
from bioio_base.reader_metadata import ReaderMetadata

###############################################################################
Expand All @@ -32,33 +31,20 @@ class PluginEntry(NamedTuple):


# global cache of plugins
plugin_cache: List[PluginEntry] = []
# global cache of plugins by extension
# note there can be multiple readers for the same extension
plugins_by_ext: Dict[str, List[PluginEntry]] = {}
plugins_by_ext_cache: Dict[str, List[PluginEntry]] = {}


def insert_sorted_by_timestamp(list: List[PluginEntry], item: PluginEntry) -> None:
"""
Insert into list of PluginEntrys sorted by their timestamps (install dates)
"""
for i, other in enumerate(list):
if item.timestamp > other.timestamp:
list.insert(i, item)
return
list.append(item)


def add_plugin(pluginentry: PluginEntry) -> None:
plugin_cache.append(pluginentry)
exts = pluginentry.metadata.get_supported_extensions()
for ext in exts:
if ext not in plugins_by_ext:
plugins_by_ext[ext] = [pluginentry]
continue

# insert in sorted order (sorted by most recently installed)
pluginlist = plugins_by_ext[ext]
insert_sorted_by_timestamp(pluginlist, pluginentry)


def get_dependency_version_range_for_distribution(
distribution_name: str, dependency_name: str
) -> Tuple[Optional[str], Optional[str]]:
Expand Down Expand Up @@ -113,8 +99,20 @@ def get_dependency_version_range_for_distribution(
return minimum_dependency_version, maximum_dependency_version


def get_plugins() -> List[PluginEntry]:
def get_plugins(use_cache: bool) -> Dict[str, List[PluginEntry]]:
"""
Gather a mapping from support file extensions
to plugins installed that support said extension
"""
if use_cache and plugins_by_ext_cache:
return plugins_by_ext_cache.copy()

plugins = entry_points(group="bioio.readers")

# Mapping of extensions -> applicable plugins
# note there can be multiple readers for the same extension
plugins_by_ext: Dict[str, List[PluginEntry]] = {}

(
min_compatible_bioio_base_version,
_,
Expand Down Expand Up @@ -157,15 +155,32 @@ def get_plugins() -> List[PluginEntry]:
timestamp = 0.0

# Add plugin entry
add_plugin(PluginEntry(plugin, reader_meta, timestamp))
plugin_entry = PluginEntry(plugin, reader_meta, timestamp)
for ext in plugin_entry.metadata.get_supported_extensions():
if ext not in plugins_by_ext:
plugins_by_ext[ext] = [plugin_entry]
continue

# insert in sorted order (sorted by most recently installed)
pluginlist = plugins_by_ext[ext]
insert_sorted_by_timestamp(pluginlist, plugin_entry)

return plugin_cache
# Save copy of plugins to cache then return
plugins_by_ext_cache.clear()
plugins_by_ext_cache.update(plugins_by_ext)
return plugins_by_ext


def dump_plugins() -> None:
# TODO don't call get_plugins every time
get_plugins()
for plugin in plugin_cache:
"""
Report information about plugins currently installed
"""
plugins_by_ext = get_plugins(use_cache=True)
plugin_set = set()
for _, plugins in plugins_by_ext.items():
plugin_set.update(plugins)

for plugin in plugin_set:
ep = plugin.entrypoint
print(ep.name)

Expand Down Expand Up @@ -196,36 +211,3 @@ def dump_plugins() -> None:
for ext in sorted_exts:
plugins = plugins_by_ext[ext]
print(f"{ext}: {plugins}")


def find_reader_for_path(path: str) -> Optional[Reader]:
candidates = find_readers_for_path(path)
for candidate in candidates:
reader = candidate.metadata.get_reader()
if reader.is_supported_image(
path,
# TODO fs_kwargs=fs_kwargs,
):
return reader
return None

# try to match on the longest possible registered extension
# exts = sorted(plugins_by_ext.keys(), key=len, reverse=True)
# for ext in exts:
# if path.endswith(ext):
# candidates = plugins_by_ext[ext]
# # TODO select a candidate by some criteria?
# return candidates[0]
# # didn't find a reader for this extension
# return None


def find_readers_for_path(path: str) -> List[PluginEntry]:
candidates: List[PluginEntry] = []
# try to match on the longest possible registered extension first
exts = sorted(plugins_by_ext.keys(), key=len, reverse=True)
for ext in exts:
if path.endswith(ext):
candidates = candidates + plugins_by_ext[ext]
print(candidates)
return candidates

0 comments on commit 0bb4107

Please sign in to comment.