Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feature/require minimum bioio-base for plugins #7

Merged
merged 2 commits into from
Jul 18, 2023
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
119 changes: 103 additions & 16 deletions bioio/plugins.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,22 +2,28 @@
# -*- coding: utf-8 -*-

import os
import re
import sys
from datetime import datetime
from pathlib import Path

import semver

if sys.version_info < (3, 10):
from importlib_metadata import EntryPoint, entry_points
from importlib_metadata import EntryPoint, entry_points, requires
else:
from importlib.metadata import entry_points, EntryPoint
from importlib.metadata import entry_points, EntryPoint, requires

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

from bioio_types.reader import Reader
from bioio_types.reader_metadata import ReaderMetadata

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

BIOIO_DIST_NAME = "bioio"
BIOIO_BASE_DIST_NAME = "bioio-types" # TODO: Rename to bioio-base


class PluginEntry(NamedTuple):
entrypoint: EntryPoint
Expand Down Expand Up @@ -53,24 +59,105 @@ def add_plugin(pluginentry: PluginEntry) -> None:
insert_sorted_by_timestamp(pluginlist, pluginentry)


def get_dependency_version_range_for_distribution(
distribution_name: str, dependency_name: str
) -> Tuple[Optional[str], Optional[str]]:
"""
Retrieves the minimum and maximum versions of the dependency as specified
by the given distribution.
`None` as the minimum or maximum represents no minimum or maximum (respectively)
"""
all_distribution_deps = requires(distribution_name) or []
matching_distribution_deps = [
dep for dep in all_distribution_deps if dep.startswith(dependency_name)
]
if len(matching_distribution_deps) != 1:
raise ValueError(
f"Expected to find 1 `{dependency_name}` dependency for "
f"`{distribution_name}`, instead found {len(matching_distribution_deps)}"
)

# Versions from importlib are formatted like "<dependency name> (<0.19,>=0.18)"
# so this regex needs to find the inner part of the parentheses to get the
# version specifications
version_specification_match = re.findall(r"\(.*?\)", matching_distribution_deps[0])

minimum_dependency_version = None
maximum_dependency_version = None
if version_specification_match:
# A distribution can specify a minimum and maximum (or force an exact) version
# so multiple versions need to be parsed here
version_specifications = version_specification_match[0].split(",")
for version_specification in version_specifications:
version = "".join(
[
char
for char in version_specification
if char.isnumeric() or char == "."
]
)

# Unfortunately not all versions are specified in full SemVer format
# and instead can be like "0.19" and the "semver" package does not
# handle these well so we can just fill in the missing .0
# to make 0.19 -> 0.19.0
while version.count(".") < 2:
version += ".0"

if "<" in version_specification or "==" in version_specification:
maximum_dependency_version = version

if ">" in version_specification or "==" in version_specification:
minimum_dependency_version = version

return minimum_dependency_version, maximum_dependency_version


def get_plugins() -> List[PluginEntry]:
plugins = entry_points(group="bioio.readers")
(
min_compatible_bioio_base_version,
_,
) = get_dependency_version_range_for_distribution(
BIOIO_DIST_NAME, BIOIO_BASE_DIST_NAME
)
for plugin in plugins:
# ReaderMetadata knows how to instantiate the actual Reader
reader_meta = plugin.load().ReaderMetadata
if plugin.dist is not None:
files = plugin.dist.files
if files is not None:
firstfile = files[0]
timestamp = os.path.getmtime(Path(firstfile.locate()).parent)
else:
print(f"No files found for plugin: '{plugin}'")
(
_,
max_bioio_base_version_for_plugin,
) = get_dependency_version_range_for_distribution(
plugin.name, BIOIO_BASE_DIST_NAME
)
if (
min_compatible_bioio_base_version is not None
and max_bioio_base_version_for_plugin is not None
and semver.compare(
max_bioio_base_version_for_plugin,
min_compatible_bioio_base_version,
)
< 0
):
print(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

at some point these prints should maybe be logs instead; not necessarily for this pr

f"Plugin `{plugin.name}` does not meet "
f"minimum `{BIOIO_BASE_DIST_NAME}` of "
f"{min_compatible_bioio_base_version}, ignoring"
)
else:
print(f"Could not find distribution for plugin: '{plugin}'")
timestamp = 0.0
# ReaderMetadata knows how to instantiate the actual Reader
reader_meta = plugin.load().ReaderMetadata
if plugin.dist is not None:
files = plugin.dist.files
if files is not None:
firstfile = files[0]
timestamp = os.path.getmtime(Path(firstfile.locate()).parent)
else:
print(f"No files found for plugin: '{plugin}'")
else:
print(f"Could not find distribution for plugin: '{plugin}'")
timestamp = 0.0

# Add plugin entry
add_plugin(PluginEntry(plugin, reader_meta, timestamp))
# Add plugin entry
add_plugin(PluginEntry(plugin, reader_meta, timestamp))

return plugin_cache

Expand Down
3 changes: 2 additions & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,8 @@ classifiers = [
]
dynamic = ["version"]
dependencies = [
"bioio-types",
"bioio-types>=0.0.2",
"semver>=3.0.1",
]

[project.urls]
Expand Down