liblaf-cherries 0.1.6__py3-none-any.whl → 0.2.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- liblaf/cherries/__init__.pyi +8 -87
- liblaf/cherries/_entrypoint.py +56 -0
- liblaf/cherries/_version.py +2 -2
- liblaf/cherries/config/_asset.py +3 -3
- liblaf/cherries/core/__init__.pyi +14 -6
- liblaf/cherries/core/_impl.py +58 -0
- liblaf/cherries/core/_plugin.py +96 -0
- liblaf/cherries/core/_run.py +42 -0
- liblaf/cherries/core/_spec.py +53 -159
- liblaf/cherries/core/typed.py +2 -0
- liblaf/cherries/meta/_git.py +3 -3
- liblaf/cherries/meta/_name.py +3 -3
- liblaf/cherries/{pathutils → paths}/_path.py +9 -7
- liblaf/cherries/plugins/__init__.pyi +3 -0
- liblaf/cherries/plugins/logging.py +25 -0
- liblaf/cherries/profiles/__init__.pyi +13 -0
- liblaf/cherries/profiles/_abc.py +10 -0
- liblaf/cherries/profiles/_default.py +12 -0
- liblaf/cherries/profiles/_factory.py +21 -0
- liblaf/cherries/profiles/_playground.py +13 -0
- {liblaf_cherries-0.1.6.dist-info → liblaf_cherries-0.2.0.dist-info}/METADATA +5 -3
- liblaf_cherries-0.2.0.dist-info/RECORD +43 -0
- liblaf/cherries/_run.py +0 -51
- liblaf/cherries/core/_exp.py +0 -119
- liblaf/cherries/integration/__init__.pyi +0 -72
- liblaf/cherries/integration/_abc.py +0 -144
- liblaf/cherries/integration/_exp.py +0 -109
- liblaf/cherries/integration/comet.py +0 -142
- liblaf/cherries/integration/dvc.py +0 -51
- liblaf/cherries/integration/git.py +0 -44
- liblaf/cherries/integration/logging.py +0 -45
- liblaf/cherries/presets/__init__.pyi +0 -5
- liblaf/cherries/presets/_default.py +0 -46
- liblaf/cherries/presets/_playground.py +0 -11
- liblaf/cherries/presets/typed.py +0 -5
- liblaf_cherries-0.1.6.dist-info/RECORD +0 -44
- /liblaf/cherries/{integration → paths}/__init__.py +0 -0
- /liblaf/cherries/{pathutils → paths}/__init__.pyi +0 -0
- /liblaf/cherries/{pathutils → paths}/_convert.py +0 -0
- /liblaf/cherries/{pathutils → paths}/_special.py +0 -0
- /liblaf/cherries/{pathutils → plugins}/__init__.py +0 -0
- /liblaf/cherries/{presets → profiles}/__init__.py +0 -0
- {liblaf_cherries-0.1.6.dist-info → liblaf_cherries-0.2.0.dist-info}/WHEEL +0 -0
- {liblaf_cherries-0.1.6.dist-info → liblaf_cherries-0.2.0.dist-info}/licenses/LICENSE +0 -0
liblaf/cherries/__init__.pyi
CHANGED
@@ -1,47 +1,8 @@
|
|
1
|
-
from . import config, core,
|
2
|
-
from .
|
3
|
-
from .config import
|
4
|
-
|
5
|
-
|
6
|
-
MetaAsset,
|
7
|
-
PathProvider,
|
8
|
-
get_assets,
|
9
|
-
get_inputs,
|
10
|
-
get_outputs,
|
11
|
-
input, # noqa: A004
|
12
|
-
output,
|
13
|
-
)
|
14
|
-
from .core import ConcreteImpl, ImplDef, SpecDef, impl, spec
|
15
|
-
from .integration import (
|
16
|
-
AddTag,
|
17
|
-
AddTags,
|
18
|
-
End,
|
19
|
-
Experiment,
|
20
|
-
LogAsset,
|
21
|
-
LogCode,
|
22
|
-
LogMetric,
|
23
|
-
LogMetrics,
|
24
|
-
LogOther,
|
25
|
-
LogOthers,
|
26
|
-
LogParam,
|
27
|
-
LogParams,
|
28
|
-
Plugin,
|
29
|
-
Start,
|
30
|
-
add_tag,
|
31
|
-
add_tags,
|
32
|
-
current_exp,
|
33
|
-
log_asset,
|
34
|
-
log_code,
|
35
|
-
log_input,
|
36
|
-
log_metric,
|
37
|
-
log_metrics,
|
38
|
-
log_other,
|
39
|
-
log_others,
|
40
|
-
log_output,
|
41
|
-
log_param,
|
42
|
-
log_params,
|
43
|
-
)
|
44
|
-
from .pathutils import (
|
1
|
+
from . import config, core, meta, paths
|
2
|
+
from ._entrypoint import end, run, start
|
3
|
+
from .config import BaseConfig, input, output # noqa: A004
|
4
|
+
from .core import Plugin, Run, active_run, log_asset, log_metrics
|
5
|
+
from .paths import (
|
45
6
|
as_os_path,
|
46
7
|
as_path,
|
47
8
|
as_posix,
|
@@ -58,72 +19,32 @@ from .pathutils import (
|
|
58
19
|
)
|
59
20
|
|
60
21
|
__all__ = [
|
61
|
-
"AddTag",
|
62
|
-
"AddTags",
|
63
|
-
"AssetKind",
|
64
22
|
"BaseConfig",
|
65
|
-
"ConcreteImpl",
|
66
|
-
"End",
|
67
|
-
"Experiment",
|
68
|
-
"ImplDef",
|
69
|
-
"LogAsset",
|
70
|
-
"LogCode",
|
71
|
-
"LogMetric",
|
72
|
-
"LogMetrics",
|
73
|
-
"LogOther",
|
74
|
-
"LogOthers",
|
75
|
-
"LogParam",
|
76
|
-
"LogParams",
|
77
|
-
"MetaAsset",
|
78
|
-
"PathProvider",
|
79
23
|
"Plugin",
|
80
|
-
"
|
81
|
-
"
|
82
|
-
"add_tag",
|
83
|
-
"add_tags",
|
24
|
+
"Run",
|
25
|
+
"active_run",
|
84
26
|
"as_os_path",
|
85
27
|
"as_path",
|
86
28
|
"as_posix",
|
87
29
|
"config",
|
88
30
|
"core",
|
89
|
-
"current_exp",
|
90
31
|
"data",
|
91
32
|
"end",
|
92
|
-
"end",
|
93
|
-
"end",
|
94
33
|
"entrypoint",
|
95
34
|
"exp_dir",
|
96
|
-
"get_assets",
|
97
|
-
"get_inputs",
|
98
|
-
"get_outputs",
|
99
35
|
"git_root",
|
100
36
|
"git_root_safe",
|
101
|
-
"impl",
|
102
37
|
"input",
|
103
38
|
"inputs",
|
104
|
-
"integration",
|
105
39
|
"log_asset",
|
106
|
-
"log_code",
|
107
|
-
"log_input",
|
108
|
-
"log_metric",
|
109
40
|
"log_metrics",
|
110
|
-
"log_other",
|
111
|
-
"log_others",
|
112
|
-
"log_output",
|
113
|
-
"log_param",
|
114
|
-
"log_params",
|
115
41
|
"meta",
|
116
42
|
"output",
|
117
43
|
"outputs",
|
118
44
|
"params",
|
119
45
|
"path",
|
120
|
-
"
|
121
|
-
"presets",
|
122
|
-
"run",
|
46
|
+
"paths",
|
123
47
|
"run",
|
124
|
-
"spec",
|
125
48
|
"src",
|
126
49
|
"start",
|
127
|
-
"start",
|
128
|
-
"utils",
|
129
50
|
]
|
@@ -0,0 +1,56 @@
|
|
1
|
+
import inspect
|
2
|
+
from collections.abc import Callable, Mapping, Sequence
|
3
|
+
from typing import Any
|
4
|
+
|
5
|
+
from liblaf.cherries import core, profiles
|
6
|
+
|
7
|
+
|
8
|
+
def end() -> None:
|
9
|
+
core.active_run.end()
|
10
|
+
|
11
|
+
|
12
|
+
def run[T](main: Callable[..., T], *, profile: profiles.ProfileLike | None = None) -> T:
|
13
|
+
run: core.Run = start(profile=profile)
|
14
|
+
args: Sequence[Any]
|
15
|
+
kwargs: Mapping[str, Any]
|
16
|
+
args, kwargs = _make_args(main)
|
17
|
+
# TODO: log config & inputs
|
18
|
+
result: T = main(*args, **kwargs)
|
19
|
+
# TODO: log outputs
|
20
|
+
run.end()
|
21
|
+
return result
|
22
|
+
|
23
|
+
|
24
|
+
def start(
|
25
|
+
profile: profiles.ProfileLike | None = None,
|
26
|
+
) -> core.Run:
|
27
|
+
run: core.Run = profiles.factory(profile).init()
|
28
|
+
run.start()
|
29
|
+
# TODO: log metadata
|
30
|
+
return run
|
31
|
+
|
32
|
+
|
33
|
+
def _make_args(func: Callable) -> tuple[Sequence[Any], Mapping[str, Any]]:
|
34
|
+
signature: inspect.Signature = inspect.signature(func)
|
35
|
+
args: list[Any] = []
|
36
|
+
kwargs: dict[str, Any] = {}
|
37
|
+
for name, param in signature.parameters.items():
|
38
|
+
match param.kind:
|
39
|
+
case (
|
40
|
+
inspect.Parameter.POSITIONAL_ONLY
|
41
|
+
| inspect.Parameter.POSITIONAL_OR_KEYWORD
|
42
|
+
):
|
43
|
+
args.append(_make_arg(param))
|
44
|
+
case inspect.Parameter.KEYWORD_ONLY:
|
45
|
+
kwargs[name] = _make_arg(param)
|
46
|
+
case _:
|
47
|
+
pass
|
48
|
+
return args, kwargs
|
49
|
+
|
50
|
+
|
51
|
+
def _make_arg(param: inspect.Parameter) -> Any:
|
52
|
+
if param.default is not inspect.Parameter.empty:
|
53
|
+
return param.default
|
54
|
+
if param.annotation is not inspect.Parameter.empty:
|
55
|
+
return param.annotation()
|
56
|
+
return None
|
liblaf/cherries/_version.py
CHANGED
liblaf/cherries/config/_asset.py
CHANGED
@@ -6,7 +6,7 @@ from typing import Any
|
|
6
6
|
import pydantic
|
7
7
|
|
8
8
|
from liblaf import grapes
|
9
|
-
from liblaf.cherries import
|
9
|
+
from liblaf.cherries import paths
|
10
10
|
from liblaf.cherries.typed import PathLike
|
11
11
|
|
12
12
|
|
@@ -61,12 +61,12 @@ def get_outputs(cfg: pydantic.BaseModel) -> list[Path]:
|
|
61
61
|
|
62
62
|
|
63
63
|
def input(path: PathLike, extra: PathProvider | None = None, **kwargs) -> Path: # noqa: A001
|
64
|
-
field_info: pydantic.fields.FieldInfo = pydantic.Field(
|
64
|
+
field_info: pydantic.fields.FieldInfo = pydantic.Field(paths.data(path), **kwargs) # pyright: ignore[reportAssignmentType]
|
65
65
|
field_info.metadata.append(MetaAsset(kind=AssetKind.INPUT, extra=extra))
|
66
66
|
return field_info # pyright: ignore[reportReturnType]
|
67
67
|
|
68
68
|
|
69
69
|
def output(path: PathLike, extra: PathProvider | None = None, **kwargs) -> Path:
|
70
|
-
field_info: pydantic.fields.FieldInfo = pydantic.Field(
|
70
|
+
field_info: pydantic.fields.FieldInfo = pydantic.Field(paths.data(path), **kwargs) # pyright: ignore[reportAssignmentType]
|
71
71
|
field_info.metadata.append(MetaAsset(kind=AssetKind.OUTPUT, extra=extra))
|
72
72
|
return field_info # pyright: ignore[reportReturnType]
|
@@ -1,12 +1,20 @@
|
|
1
|
-
from .
|
2
|
-
from .
|
1
|
+
from ._impl import ImplInfo, get_impl_info, impl
|
2
|
+
from ._plugin import Plugin
|
3
|
+
from ._run import Run, active_run, log_asset, log_metrics
|
4
|
+
from ._spec import SpecInfo, spec
|
5
|
+
from .typed import MethodName, PluginId
|
3
6
|
|
4
7
|
__all__ = [
|
5
|
-
"
|
6
|
-
"
|
7
|
-
"ImplDef",
|
8
|
+
"ImplInfo",
|
9
|
+
"MethodName",
|
8
10
|
"Plugin",
|
9
|
-
"
|
11
|
+
"PluginId",
|
12
|
+
"Run",
|
13
|
+
"SpecInfo",
|
14
|
+
"active_run",
|
15
|
+
"get_impl_info",
|
10
16
|
"impl",
|
17
|
+
"log_asset",
|
18
|
+
"log_metrics",
|
11
19
|
"spec",
|
12
20
|
]
|
@@ -0,0 +1,58 @@
|
|
1
|
+
import functools
|
2
|
+
from collections.abc import Callable, Iterable
|
3
|
+
from typing import Any, overload
|
4
|
+
|
5
|
+
import attrs
|
6
|
+
|
7
|
+
from liblaf import grapes
|
8
|
+
|
9
|
+
from .typed import PluginId
|
10
|
+
|
11
|
+
|
12
|
+
@attrs.define
|
13
|
+
class ImplInfo:
|
14
|
+
after: Iterable[PluginId] = attrs.field(default=())
|
15
|
+
before: Iterable[PluginId] = attrs.field(default=())
|
16
|
+
priority: int = 0
|
17
|
+
|
18
|
+
|
19
|
+
@overload
|
20
|
+
def impl[C: Callable](
|
21
|
+
func: C,
|
22
|
+
/,
|
23
|
+
*,
|
24
|
+
priority: int = 0,
|
25
|
+
after: Iterable[PluginId] = (),
|
26
|
+
before: Iterable[PluginId] = (),
|
27
|
+
) -> C: ...
|
28
|
+
@overload
|
29
|
+
def impl[C: Callable](
|
30
|
+
*,
|
31
|
+
priority: int = 0,
|
32
|
+
after: Iterable[PluginId] = (),
|
33
|
+
before: Iterable[PluginId] = (),
|
34
|
+
) -> Callable[[C], C]: ...
|
35
|
+
def impl(
|
36
|
+
func: Callable | None = None,
|
37
|
+
/,
|
38
|
+
priority: int = 0,
|
39
|
+
after: Iterable[PluginId] = (),
|
40
|
+
before: Iterable[PluginId] = (),
|
41
|
+
) -> Any:
|
42
|
+
if func is None:
|
43
|
+
return functools.partial(impl, priority=priority, after=after, before=before)
|
44
|
+
info = ImplInfo(after=after, before=before, priority=priority)
|
45
|
+
|
46
|
+
@grapes.decorator(attrs={"_self_impl": info})
|
47
|
+
def wrapper(
|
48
|
+
wrapped: Callable, _instance: Any, args: tuple, kwargs: dict[str, Any]
|
49
|
+
) -> Any:
|
50
|
+
return wrapped(*args, **kwargs)
|
51
|
+
|
52
|
+
return wrapper(func)
|
53
|
+
|
54
|
+
|
55
|
+
def get_impl_info(func: Callable | None) -> ImplInfo | None:
|
56
|
+
if func is None:
|
57
|
+
return None
|
58
|
+
return getattr(func, "_self_impl", None)
|
@@ -0,0 +1,96 @@
|
|
1
|
+
import inspect
|
2
|
+
from collections.abc import Mapping, MutableMapping, Sequence
|
3
|
+
from typing import Any, Self
|
4
|
+
|
5
|
+
import attrs
|
6
|
+
import networkx as nx
|
7
|
+
|
8
|
+
from ._impl import ImplInfo, get_impl_info
|
9
|
+
from ._spec import SpecInfo
|
10
|
+
from .typed import MethodName
|
11
|
+
|
12
|
+
|
13
|
+
@attrs.define
|
14
|
+
class Plugin:
|
15
|
+
plugins: dict[str, "Plugin"] = attrs.field(factory=dict)
|
16
|
+
|
17
|
+
_plugin_parent: Self | None = attrs.field(default=None)
|
18
|
+
_sort_plugins_cache: MutableMapping[MethodName, Sequence["Plugin"]] = attrs.field(
|
19
|
+
factory=dict, init=False
|
20
|
+
)
|
21
|
+
|
22
|
+
@classmethod
|
23
|
+
def plugin_id_cls(cls) -> str:
|
24
|
+
return cls.__name__
|
25
|
+
|
26
|
+
@property
|
27
|
+
def plugin_id(self) -> str:
|
28
|
+
return self.plugin_id_cls()
|
29
|
+
|
30
|
+
@property
|
31
|
+
def plugin_root(self) -> Self:
|
32
|
+
if self._plugin_parent is None:
|
33
|
+
return self
|
34
|
+
return self._plugin_parent.plugin_root
|
35
|
+
|
36
|
+
@property
|
37
|
+
def specs(self) -> dict[str, SpecInfo]:
|
38
|
+
return {
|
39
|
+
name: method._self_spec # noqa: SLF001
|
40
|
+
for name, method in inspect.getmembers(
|
41
|
+
type(self), lambda m: getattr(m, "_self_spec", None) is not None
|
42
|
+
)
|
43
|
+
}
|
44
|
+
|
45
|
+
def delegate(
|
46
|
+
self,
|
47
|
+
method: MethodName,
|
48
|
+
args: Sequence[Any] = (),
|
49
|
+
kwargs: Mapping[str, Any] = {},
|
50
|
+
*,
|
51
|
+
first_result: bool = False,
|
52
|
+
) -> Any:
|
53
|
+
results: list[Any] = []
|
54
|
+
for plugin in self._sort_plugins_cache.get(method, []):
|
55
|
+
result: Any = getattr(plugin, method)(*args, **kwargs)
|
56
|
+
if result is None:
|
57
|
+
continue
|
58
|
+
if first_result:
|
59
|
+
return result
|
60
|
+
results.append(result)
|
61
|
+
return results
|
62
|
+
|
63
|
+
def register(self, plugin: "Plugin") -> None:
|
64
|
+
plugin._plugin_parent = self # noqa: SLF001
|
65
|
+
self.plugins[plugin.plugin_id] = plugin
|
66
|
+
|
67
|
+
def _prepare(self) -> None:
|
68
|
+
for method in self.specs:
|
69
|
+
self._sort_plugins_cache[method] = self._sort_plugins(
|
70
|
+
method, refresh_cache=True
|
71
|
+
)
|
72
|
+
|
73
|
+
def _sort_plugins(
|
74
|
+
self, method: str, *, refresh_cache: bool = False
|
75
|
+
) -> Sequence["Plugin"]:
|
76
|
+
if refresh_cache or method not in self._sort_plugins_cache:
|
77
|
+
plugin_infos: dict[str, ImplInfo] = {
|
78
|
+
plugin_id: info
|
79
|
+
for plugin_id, plugin in self.plugins.items()
|
80
|
+
if (info := get_impl_info(getattr(plugin, method, None))) is not None
|
81
|
+
}
|
82
|
+
graph = nx.DiGraph()
|
83
|
+
for plugin_id, impl_info in plugin_infos.items():
|
84
|
+
graph.add_node(plugin_id)
|
85
|
+
for after in impl_info.after:
|
86
|
+
graph.add_edge(after, plugin_id)
|
87
|
+
for before in impl_info.before:
|
88
|
+
graph.add_edge(plugin_id, before)
|
89
|
+
self._sort_plugins_cache[method] = tuple(
|
90
|
+
plugin
|
91
|
+
for plugin_id in nx.lexicographical_topological_sort(
|
92
|
+
graph, key=lambda node: plugin_infos[node].priority
|
93
|
+
)
|
94
|
+
if (plugin := self.plugins.get(plugin_id)) is not None
|
95
|
+
)
|
96
|
+
return self._sort_plugins_cache[method]
|
@@ -0,0 +1,42 @@
|
|
1
|
+
from pathlib import Path
|
2
|
+
|
3
|
+
import attrs
|
4
|
+
|
5
|
+
from liblaf.cherries import paths
|
6
|
+
|
7
|
+
from ._plugin import Plugin
|
8
|
+
from ._spec import spec
|
9
|
+
|
10
|
+
|
11
|
+
@attrs.define
|
12
|
+
class Run(Plugin):
|
13
|
+
""".
|
14
|
+
|
15
|
+
References:
|
16
|
+
1. [Experiment - Comet Docs](https://www.comet.com/docs/v2/api-and-sdk/python-sdk/reference/Experiment/)
|
17
|
+
2. [Logger | ClearML](https://clear.ml/docs/latest/docs/references/sdk/logger)
|
18
|
+
3. [MLflow Tracking APIs | MLflow](https://www.mlflow.org/docs/latest/ml/tracking/tracking-api/)
|
19
|
+
"""
|
20
|
+
|
21
|
+
@property
|
22
|
+
def exp_dir(self) -> Path:
|
23
|
+
return paths.exp_dir(absolute=True)
|
24
|
+
|
25
|
+
@spec
|
26
|
+
def end(self, *args, **kwargs) -> None: ...
|
27
|
+
|
28
|
+
@spec
|
29
|
+
def log_asset(self, *args, **kwargs) -> None: ...
|
30
|
+
|
31
|
+
@spec
|
32
|
+
def log_metrics(self, *args, **kwargs) -> None: ...
|
33
|
+
|
34
|
+
@spec(delegate=False)
|
35
|
+
def start(self, *args, **kwargs) -> None:
|
36
|
+
self._prepare()
|
37
|
+
self.delegate("start", args, kwargs)
|
38
|
+
|
39
|
+
|
40
|
+
active_run: Run = Run()
|
41
|
+
log_asset = active_run.log_asset
|
42
|
+
log_metrics = active_run.log_metrics
|
liblaf/cherries/core/_spec.py
CHANGED
@@ -1,177 +1,71 @@
|
|
1
1
|
import functools
|
2
2
|
import inspect
|
3
|
-
from collections.abc import Callable,
|
4
|
-
from typing import Any,
|
3
|
+
from collections.abc import Callable, Mapping, Sequence
|
4
|
+
from typing import Any, Protocol, overload
|
5
5
|
|
6
6
|
import attrs
|
7
|
-
import networkx as nx
|
8
7
|
|
9
|
-
from liblaf
|
8
|
+
from liblaf import grapes
|
10
9
|
|
10
|
+
from .typed import MethodName
|
11
11
|
|
12
|
-
def as_seq(value: str | Iterable[str] | None = None) -> Sequence[str]:
|
13
|
-
if value is None:
|
14
|
-
return ()
|
15
|
-
if isinstance(value, (str, bytes)):
|
16
|
-
return (value,)
|
17
|
-
return tuple(value)
|
18
12
|
|
13
|
+
class Plugin(Protocol):
|
14
|
+
def delegate(
|
15
|
+
self,
|
16
|
+
method: MethodName,
|
17
|
+
args: Sequence[Any],
|
18
|
+
kwargs: Mapping[str, Any],
|
19
|
+
*,
|
20
|
+
first_result: bool = False,
|
21
|
+
) -> Any: ...
|
19
22
|
|
20
|
-
@attrs.frozen
|
21
|
-
class SpecDef:
|
22
|
-
name: str
|
23
|
-
firstresult: bool
|
24
23
|
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
after: str | Sequence[str] = attrs.field(default=(), converter=as_seq)
|
30
|
-
before: str | Sequence[str] = attrs.field(default=(), converter=as_seq)
|
31
|
-
priority: int = 0
|
32
|
-
|
33
|
-
|
34
|
-
@attrs.frozen
|
35
|
-
class ConcreteImpl[C: Callable](ImplDef):
|
36
|
-
fn: C = attrs.field(kw_only=True)
|
37
|
-
plugin_name: str = attrs.field(kw_only=True)
|
38
|
-
|
39
|
-
|
40
|
-
@attrs.define(slots=False)
|
41
|
-
class Plugin:
|
42
|
-
plugins: list[Self] = attrs.field(factory=list)
|
43
|
-
|
44
|
-
@classmethod
|
45
|
-
def get_spec(cls, name: str | SpecDef) -> SpecDef:
|
46
|
-
if isinstance(name, SpecDef):
|
47
|
-
return name
|
48
|
-
fn: Callable | None = getattr(cls, name, None)
|
49
|
-
if fn is None:
|
50
|
-
raise KeyError(name)
|
51
|
-
spec: SpecDef | None = getattr(fn, "spec", None)
|
52
|
-
if spec is None:
|
53
|
-
raise KeyError(name)
|
54
|
-
return spec
|
55
|
-
|
56
|
-
@classmethod
|
57
|
-
def iter_specs(cls) -> Generator[SpecDef]:
|
58
|
-
for _name, fn in inspect.getmembers(cls):
|
59
|
-
spec: SpecDef | None = getattr(fn, "spec", None)
|
60
|
-
if spec is None:
|
61
|
-
continue
|
62
|
-
yield spec
|
63
|
-
|
64
|
-
@property
|
65
|
-
def name(self) -> str:
|
66
|
-
return type(self).__name__
|
67
|
-
|
68
|
-
@functools.cached_property
|
69
|
-
def impls(self) -> dict[str, Sequence[ConcreteImpl]]:
|
70
|
-
return {spec.name: tuple(self.iter_impls(spec)) for spec in self.iter_specs()}
|
71
|
-
|
72
|
-
def call(self, spec: str | SpecDef, /, *args, **kwargs) -> Any:
|
73
|
-
spec: SpecDef = self.get_spec(spec)
|
74
|
-
impls: Sequence[ConcreteImpl] = self.impls.get(spec.name, ())
|
75
|
-
if spec.firstresult:
|
76
|
-
if len(impls) == 0:
|
77
|
-
return None
|
78
|
-
return impls[0].fn(*args, **kwargs)
|
79
|
-
return [impl.fn(*args, **kwargs) for impl in impls]
|
80
|
-
|
81
|
-
def get_impl(self, spec: str | SpecDef, /) -> ConcreteImpl | None:
|
82
|
-
spec: SpecDef = self.get_spec(spec)
|
83
|
-
fn: Callable | None = getattr(self, spec.name, None)
|
84
|
-
if fn is None:
|
85
|
-
return None
|
86
|
-
impl: ImplDef | None = getattr(fn, "impl", None)
|
87
|
-
if impl is None:
|
88
|
-
return None
|
89
|
-
return ConcreteImpl(
|
90
|
-
name=impl.name,
|
91
|
-
after=impl.after,
|
92
|
-
before=impl.before,
|
93
|
-
priority=impl.priority,
|
94
|
-
fn=fn,
|
95
|
-
plugin_name=self.name,
|
96
|
-
)
|
97
|
-
|
98
|
-
def iter_impls(self, spec: str | SpecDef, /) -> Generator[ConcreteImpl]:
|
99
|
-
graph = nx.DiGraph()
|
100
|
-
impls: dict[str, ConcreteImpl] = {}
|
101
|
-
for impl in self.iter_impls_unordered(spec):
|
102
|
-
impls[impl.plugin_name] = impl
|
103
|
-
graph.add_node(impl.plugin_name)
|
104
|
-
for after in impl.after:
|
105
|
-
graph.add_edge(after, impl.plugin_name)
|
106
|
-
for before in impl.before:
|
107
|
-
graph.add_edge(impl.plugin_name, before)
|
108
|
-
for impl in nx.lexicographical_topological_sort(
|
109
|
-
graph, key=lambda n: impls[n].priority if n in impls else 0
|
110
|
-
):
|
111
|
-
if impl not in impls:
|
112
|
-
continue
|
113
|
-
yield impls[impl]
|
114
|
-
|
115
|
-
def iter_impls_unordered(self, spec: str | SpecDef, /) -> Generator[ConcreteImpl]:
|
116
|
-
spec: SpecDef = self.get_spec(spec)
|
117
|
-
for plugin in self.plugins:
|
118
|
-
impl: ConcreteImpl | None = plugin.get_impl(spec)
|
119
|
-
if impl is None:
|
120
|
-
continue
|
121
|
-
yield impl
|
122
|
-
|
123
|
-
def register(self, plugin: Self) -> None:
|
124
|
-
self.plugins.append(plugin)
|
24
|
+
@attrs.define
|
25
|
+
class SpecInfo:
|
26
|
+
delegate: bool = attrs.field(default=True)
|
27
|
+
first_result: bool = attrs.field(default=False)
|
125
28
|
|
126
29
|
|
127
30
|
@overload
|
128
|
-
def spec
|
31
|
+
def spec[C: Callable](
|
32
|
+
func: C, /, *, delegate: bool = True, first_result: bool = False
|
33
|
+
) -> C: ...
|
129
34
|
@overload
|
130
|
-
def spec[C: Callable](
|
35
|
+
def spec[C: Callable](
|
36
|
+
*, delegate: bool = True, first_result: bool = False
|
37
|
+
) -> Callable[[C], C]: ...
|
131
38
|
def spec(
|
132
|
-
|
133
|
-
) -> Decorator | Callable:
|
134
|
-
if fn is None:
|
135
|
-
return functools.partial(spec, firstresult=firstresult)
|
136
|
-
|
137
|
-
@functools.wraps(fn)
|
138
|
-
def wrapper(self: Plugin, *args, **kwargs) -> Any:
|
139
|
-
return self.call(wrapper.spec, *args, **kwargs) # pyright: ignore[reportAttributeAccessIssue]
|
140
|
-
|
141
|
-
wrapper.spec = SpecDef(name=fn.__name__, firstresult=firstresult) # pyright: ignore[reportAttributeAccessIssue]
|
142
|
-
return wrapper
|
143
|
-
|
144
|
-
|
145
|
-
@overload
|
146
|
-
def impl(
|
147
|
-
*,
|
148
|
-
name: str,
|
149
|
-
after: str | Sequence[str] = (),
|
150
|
-
before: str | Sequence[str] = (),
|
151
|
-
priority: int = 0,
|
152
|
-
) -> Decorator: ...
|
153
|
-
@overload
|
154
|
-
def impl[C: Callable](
|
155
|
-
fn: C,
|
156
|
-
/,
|
157
|
-
*,
|
158
|
-
name: str,
|
159
|
-
after: str | Sequence[str] = (),
|
160
|
-
before: str | Sequence[str] = (),
|
161
|
-
priority: int = 0,
|
162
|
-
) -> C: ...
|
163
|
-
def impl(
|
164
|
-
fn: Callable | None = None,
|
39
|
+
func: Callable | None = None,
|
165
40
|
/,
|
166
41
|
*,
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
42
|
+
delegate: bool = True,
|
43
|
+
first_result: bool = False,
|
44
|
+
) -> Any:
|
45
|
+
if func is None:
|
46
|
+
return functools.partial(spec, delegate=delegate, first_result=first_result)
|
47
|
+
|
48
|
+
info = SpecInfo(delegate=delegate, first_result=first_result)
|
49
|
+
|
50
|
+
@grapes.decorator(attrs={"_self_spec": info})
|
51
|
+
def wrapper(
|
52
|
+
wrapped: Callable, instance: Plugin, args: tuple, kwargs: dict[str, Any]
|
53
|
+
) -> Any:
|
54
|
+
if info.delegate:
|
55
|
+
return instance.delegate(
|
56
|
+
wrapped.__name__, args, kwargs, first_result=info.first_result
|
57
|
+
)
|
58
|
+
return wrapped(*args, **kwargs)
|
59
|
+
|
60
|
+
return wrapper(func)
|
61
|
+
|
62
|
+
|
63
|
+
def collect_specs(cls: type[Plugin] | Plugin) -> dict[str, SpecInfo]:
|
64
|
+
if isinstance(cls, type):
|
65
|
+
cls = type(cls)
|
66
|
+
return {
|
67
|
+
name: method._self_spec # noqa: SLF001
|
68
|
+
for name, method in inspect.getmembers(
|
69
|
+
cls, lambda m: getattr(m, "_self_spec", None) is not None
|
175
70
|
)
|
176
|
-
|
177
|
-
return override(fn)
|
71
|
+
}
|
liblaf/cherries/meta/_git.py
CHANGED
@@ -3,7 +3,7 @@ import subprocess as sp
|
|
3
3
|
import git
|
4
4
|
|
5
5
|
from liblaf import grapes
|
6
|
-
from liblaf.cherries import
|
6
|
+
from liblaf.cherries import paths
|
7
7
|
|
8
8
|
|
9
9
|
def git_auto_commit(
|
@@ -40,10 +40,10 @@ def git_commit_url(sha: str | None = None) -> str:
|
|
40
40
|
|
41
41
|
def git_info() -> grapes.git.GitInfo:
|
42
42
|
info: grapes.git.GitInfo = grapes.git.info(
|
43
|
-
|
43
|
+
paths.entrypoint(absolute=True), search_parent_directories=True
|
44
44
|
)
|
45
45
|
return info
|
46
46
|
|
47
47
|
|
48
48
|
def _repo() -> git.Repo:
|
49
|
-
return git.Repo(
|
49
|
+
return git.Repo(paths.entrypoint(absolute=True), search_parent_directories=True)
|