Skip to content

Commit def6104

Browse files
authored
Merge pull request #7 from bioio-devs/feature/require-minimum-bioio-base
feature/require minimum bioio-base for plugins
2 parents 07f6d80 + c4b6581 commit def6104

File tree

2 files changed

+104
-16
lines changed

2 files changed

+104
-16
lines changed

bioio/plugins.py

+103-16
Original file line numberDiff line numberDiff line change
@@ -2,22 +2,28 @@
22
# -*- coding: utf-8 -*-
33

44
import os
5+
import re
56
import sys
67
from datetime import datetime
78
from pathlib import Path
89

10+
import semver
11+
912
if sys.version_info < (3, 10):
10-
from importlib_metadata import EntryPoint, entry_points
13+
from importlib_metadata import EntryPoint, entry_points, requires
1114
else:
12-
from importlib.metadata import entry_points, EntryPoint
15+
from importlib.metadata import entry_points, EntryPoint, requires
1316

14-
from typing import Dict, List, NamedTuple, Optional
17+
from typing import Dict, List, NamedTuple, Optional, Tuple
1518

1619
from bioio_base.reader import Reader
1720
from bioio_base.reader_metadata import ReaderMetadata
1821

1922
###############################################################################
2023

24+
BIOIO_DIST_NAME = "bioio"
25+
BIOIO_BASE_DIST_NAME = "bioio-types" # TODO: Rename to bioio-base
26+
2127

2228
class PluginEntry(NamedTuple):
2329
entrypoint: EntryPoint
@@ -53,24 +59,105 @@ def add_plugin(pluginentry: PluginEntry) -> None:
5359
insert_sorted_by_timestamp(pluginlist, pluginentry)
5460

5561

62+
def get_dependency_version_range_for_distribution(
63+
distribution_name: str, dependency_name: str
64+
) -> Tuple[Optional[str], Optional[str]]:
65+
"""
66+
Retrieves the minimum and maximum versions of the dependency as specified
67+
by the given distribution.
68+
`None` as the minimum or maximum represents no minimum or maximum (respectively)
69+
"""
70+
all_distribution_deps = requires(distribution_name) or []
71+
matching_distribution_deps = [
72+
dep for dep in all_distribution_deps if dep.startswith(dependency_name)
73+
]
74+
if len(matching_distribution_deps) != 1:
75+
raise ValueError(
76+
f"Expected to find 1 `{dependency_name}` dependency for "
77+
f"`{distribution_name}`, instead found {len(matching_distribution_deps)}"
78+
)
79+
80+
# Versions from importlib are formatted like "<dependency name> (<0.19,>=0.18)"
81+
# so this regex needs to find the inner part of the parentheses to get the
82+
# version specifications
83+
version_specification_match = re.findall(r"\(.*?\)", matching_distribution_deps[0])
84+
85+
minimum_dependency_version = None
86+
maximum_dependency_version = None
87+
if version_specification_match:
88+
# A distribution can specify a minimum and maximum (or force an exact) version
89+
# so multiple versions need to be parsed here
90+
version_specifications = version_specification_match[0].split(",")
91+
for version_specification in version_specifications:
92+
version = "".join(
93+
[
94+
char
95+
for char in version_specification
96+
if char.isnumeric() or char == "."
97+
]
98+
)
99+
100+
# Unfortunately not all versions are specified in full SemVer format
101+
# and instead can be like "0.19" and the "semver" package does not
102+
# handle these well so we can just fill in the missing .0
103+
# to make 0.19 -> 0.19.0
104+
while version.count(".") < 2:
105+
version += ".0"
106+
107+
if "<" in version_specification or "==" in version_specification:
108+
maximum_dependency_version = version
109+
110+
if ">" in version_specification or "==" in version_specification:
111+
minimum_dependency_version = version
112+
113+
return minimum_dependency_version, maximum_dependency_version
114+
115+
56116
def get_plugins() -> List[PluginEntry]:
57117
plugins = entry_points(group="bioio.readers")
118+
(
119+
min_compatible_bioio_base_version,
120+
_,
121+
) = get_dependency_version_range_for_distribution(
122+
BIOIO_DIST_NAME, BIOIO_BASE_DIST_NAME
123+
)
58124
for plugin in plugins:
59-
# ReaderMetadata knows how to instantiate the actual Reader
60-
reader_meta = plugin.load().ReaderMetadata
61-
if plugin.dist is not None:
62-
files = plugin.dist.files
63-
if files is not None:
64-
firstfile = files[0]
65-
timestamp = os.path.getmtime(Path(firstfile.locate()).parent)
66-
else:
67-
print(f"No files found for plugin: '{plugin}'")
125+
(
126+
_,
127+
max_bioio_base_version_for_plugin,
128+
) = get_dependency_version_range_for_distribution(
129+
plugin.name, BIOIO_BASE_DIST_NAME
130+
)
131+
if (
132+
min_compatible_bioio_base_version is not None
133+
and max_bioio_base_version_for_plugin is not None
134+
and semver.compare(
135+
max_bioio_base_version_for_plugin,
136+
min_compatible_bioio_base_version,
137+
)
138+
< 0
139+
):
140+
print(
141+
f"Plugin `{plugin.name}` does not meet "
142+
f"minimum `{BIOIO_BASE_DIST_NAME}` of "
143+
f"{min_compatible_bioio_base_version}, ignoring"
144+
)
68145
else:
69-
print(f"Could not find distribution for plugin: '{plugin}'")
70-
timestamp = 0.0
146+
# ReaderMetadata knows how to instantiate the actual Reader
147+
reader_meta = plugin.load().ReaderMetadata
148+
if plugin.dist is not None:
149+
files = plugin.dist.files
150+
if files is not None:
151+
firstfile = files[0]
152+
timestamp = os.path.getmtime(Path(firstfile.locate()).parent)
153+
else:
154+
print(f"No files found for plugin: '{plugin}'")
155+
else:
156+
print(f"Could not find distribution for plugin: '{plugin}'")
157+
timestamp = 0.0
71158

72-
# Add plugin entry
73-
add_plugin(PluginEntry(plugin, reader_meta, timestamp))
159+
# Add plugin entry
160+
add_plugin(PluginEntry(plugin, reader_meta, timestamp))
74161

75162
return plugin_cache
76163

pyproject.toml

+1
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ classifiers = [
2929
dynamic = ["version"]
3030
dependencies = [
3131
"bioio-base>=0.1.1",
32+
"semver>=3.0.1",
3233
]
3334

3435
[project.urls]

0 commit comments

Comments
 (0)