|
2 | 2 | # -*- coding: utf-8 -*-
|
3 | 3 |
|
4 | 4 | import os
|
| 5 | +import re |
5 | 6 | import sys
|
6 | 7 | from datetime import datetime
|
7 | 8 | from pathlib import Path
|
8 | 9 |
|
| 10 | +import semver |
| 11 | + |
9 | 12 | if sys.version_info < (3, 10):
|
10 |
| - from importlib_metadata import EntryPoint, entry_points |
| 13 | + from importlib_metadata import EntryPoint, entry_points, requires |
11 | 14 | else:
|
12 |
| - from importlib.metadata import entry_points, EntryPoint |
| 15 | + from importlib.metadata import entry_points, EntryPoint, requires |
13 | 16 |
|
14 |
| -from typing import Dict, List, NamedTuple, Optional |
| 17 | +from typing import Dict, List, NamedTuple, Optional, Tuple |
15 | 18 |
|
16 | 19 | from bioio_base.reader import Reader
|
17 | 20 | from bioio_base.reader_metadata import ReaderMetadata
|
18 | 21 |
|
19 | 22 | ###############################################################################
|
20 | 23 |
|
| 24 | +BIOIO_DIST_NAME = "bioio" |
| 25 | +BIOIO_BASE_DIST_NAME = "bioio-types" # TODO: Rename to bioio-base |
| 26 | + |
21 | 27 |
|
22 | 28 | class PluginEntry(NamedTuple):
|
23 | 29 | entrypoint: EntryPoint
|
@@ -53,24 +59,105 @@ def add_plugin(pluginentry: PluginEntry) -> None:
|
53 | 59 | insert_sorted_by_timestamp(pluginlist, pluginentry)
|
54 | 60 |
|
55 | 61 |
|
| 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 | + |
56 | 116 | def get_plugins() -> List[PluginEntry]:
|
57 | 117 | 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 | + ) |
58 | 124 | 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 | + ) |
68 | 145 | 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 |
71 | 158 |
|
72 |
| - # Add plugin entry |
73 |
| - add_plugin(PluginEntry(plugin, reader_meta, timestamp)) |
| 159 | + # Add plugin entry |
| 160 | + add_plugin(PluginEntry(plugin, reader_meta, timestamp)) |
74 | 161 |
|
75 | 162 | return plugin_cache
|
76 | 163 |
|
|
0 commit comments