1#!/usr/bin/env python3
2"""
3A function to select an appropriate plugin by passed in name or names
4
5"""
6# Imports:
7from __future__ import annotations
8
9# ##-- stdlib imports
10import datetime
11import enum
12import functools as ftz
13import importlib
14import itertools as itz
15import logging as logmod
16import pathlib as pl
17import re
18import time
19import types
20import weakref
21from importlib.metadata import EntryPoint
22from uuid import UUID, uuid1
23
24# ##-- end stdlib imports
25
26from jgdv.structs.strang import CodeReference
27
28# ##-- types
29# isort: off
30import abc
31import collections.abc
32from typing import TYPE_CHECKING, Generic, cast, assert_type, assert_never
33# Protocols:
34from typing import Protocol, runtime_checkable
35# Typing Decorators:
36from typing import no_type_check, final, override, overload
37
38if TYPE_CHECKING:
39 from jgdv import Maybe
40 from typing import Final
41 from typing import ClassVar, Any, LiteralString
42 from typing import Never, Self, Literal
43 from typing import TypeGuard
44 from collections.abc import Iterable, Iterator, Callable, Generator
45 from collections.abc import Sequence, Mapping, MutableMapping, Hashable
46 from jgdv.structs.chainguard import ChainGuard
47
48# isort: on
49# ##-- end types
50
51##-- logging
52logging = logmod.getLogger(__name__)
53##-- end logging
54
[docs]
55def plugin_selector(plugins:ChainGuard, *, # noqa: PLR0911, PLR0912
56 target:str="default",
57 fallback:Maybe[type|str|CodeReference]=None) -> Maybe[type]:
58 """ Selects and loads a plugin from a chainguard,
59 based on a target,
60 with an available fallback constructor
61
62 if the target is a suitable coderef, it is coerced and loaded instead,
63 bypassing the plugins
64 """
65 logging.debug("Selecting plugin for target: %s", target)
66
67 match target:
68 case "default":
69 pass
70 case str() as x:
71 try:
72 name = CodeReference(target)
73 return name()
74 except ImportError as _:
75 pass
76 except (AttributeError, KeyError) as _:
77 pass
78
79 match plugins:
80 case []:
81 pass
82 case [EntryPoint() as l]: # if theres only one, use that
83 return l.load()
84 case [EntryPoint() as l, *_] if target == "default": # If the preference is the default, use the first
85 return l.load()
86 case [*_] as loaders: # Otherwise, use the loader that matches the preferred's name
87 matching = [x for x in loaders if x.name == target]
88 if bool(matching):
89 return matching[0].load()
90 else:
91 msg = "No matching plugin for target"
92 raise ValueError(msg, target)
93 case type() as x:
94 return x
95 case _:
96 msg = "Unknown type passed to plugin selector"
97 raise TypeError(msg, plugins)
98
99 match fallback:
100 case str():
101 coderef = CodeReference(fallback)
102 return coderef()
103 case CodeReference():
104 return fallback()
105 case type():
106 return fallback
107 case _:
108 msg = "No Available Plugin, and no fallback constructor"
109 raise ValueError(msg, target, fallback, plugins)