liblaf-cherries 0.1.7__py3-none-any.whl → 0.2.1__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.
Files changed (44) hide show
  1. liblaf/cherries/__init__.pyi +8 -87
  2. liblaf/cherries/_entrypoint.py +56 -0
  3. liblaf/cherries/_version.py +2 -2
  4. liblaf/cherries/config/_asset.py +3 -3
  5. liblaf/cherries/core/__init__.pyi +14 -6
  6. liblaf/cherries/core/_impl.py +58 -0
  7. liblaf/cherries/core/_plugin.py +96 -0
  8. liblaf/cherries/core/_run.py +42 -0
  9. liblaf/cherries/core/_spec.py +53 -159
  10. liblaf/cherries/core/typed.py +2 -0
  11. liblaf/cherries/meta/_git.py +3 -3
  12. liblaf/cherries/meta/_name.py +3 -3
  13. liblaf/cherries/{pathutils → paths}/_path.py +9 -7
  14. liblaf/cherries/plugins/__init__.pyi +3 -0
  15. liblaf/cherries/plugins/logging.py +25 -0
  16. liblaf/cherries/profiles/__init__.pyi +13 -0
  17. liblaf/cherries/profiles/_abc.py +10 -0
  18. liblaf/cherries/profiles/_default.py +12 -0
  19. liblaf/cherries/profiles/_factory.py +21 -0
  20. liblaf/cherries/profiles/_playground.py +13 -0
  21. {liblaf_cherries-0.1.7.dist-info → liblaf_cherries-0.2.1.dist-info}/METADATA +5 -3
  22. liblaf_cherries-0.2.1.dist-info/RECORD +43 -0
  23. liblaf/cherries/_run.py +0 -51
  24. liblaf/cherries/core/_exp.py +0 -119
  25. liblaf/cherries/integration/__init__.pyi +0 -72
  26. liblaf/cherries/integration/_abc.py +0 -144
  27. liblaf/cherries/integration/_exp.py +0 -109
  28. liblaf/cherries/integration/comet.py +0 -142
  29. liblaf/cherries/integration/dvc.py +0 -51
  30. liblaf/cherries/integration/git.py +0 -44
  31. liblaf/cherries/integration/logging.py +0 -45
  32. liblaf/cherries/presets/__init__.pyi +0 -5
  33. liblaf/cherries/presets/_default.py +0 -46
  34. liblaf/cherries/presets/_playground.py +0 -11
  35. liblaf/cherries/presets/typed.py +0 -5
  36. liblaf_cherries-0.1.7.dist-info/RECORD +0 -44
  37. /liblaf/cherries/{integration → paths}/__init__.py +0 -0
  38. /liblaf/cherries/{pathutils → paths}/__init__.pyi +0 -0
  39. /liblaf/cherries/{pathutils → paths}/_convert.py +0 -0
  40. /liblaf/cherries/{pathutils → paths}/_special.py +0 -0
  41. /liblaf/cherries/{pathutils → plugins}/__init__.py +0 -0
  42. /liblaf/cherries/{presets → profiles}/__init__.py +0 -0
  43. {liblaf_cherries-0.1.7.dist-info → liblaf_cherries-0.2.1.dist-info}/WHEEL +0 -0
  44. {liblaf_cherries-0.1.7.dist-info → liblaf_cherries-0.2.1.dist-info}/licenses/LICENSE +0 -0
@@ -1,47 +1,8 @@
1
- from . import config, core, integration, meta, pathutils, presets, utils
2
- from ._run import end, run, start
3
- from .config import (
4
- AssetKind,
5
- BaseConfig,
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
- "SpecDef",
81
- "Start",
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
- "pathutils",
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
@@ -17,5 +17,5 @@ __version__: str
17
17
  __version_tuple__: VERSION_TUPLE
18
18
  version_tuple: VERSION_TUPLE
19
19
 
20
- __version__ = version = '0.1.7'
21
- __version_tuple__ = version_tuple = (0, 1, 7)
20
+ __version__ = version = '0.2.1'
21
+ __version_tuple__ = version_tuple = (0, 2, 1)
@@ -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 pathutils as _path
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(_path.data(path), **kwargs) # pyright: ignore[reportAssignmentType]
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(_path.data(path), **kwargs) # pyright: ignore[reportAssignmentType]
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 ._exp import Experiment
2
- from ._spec import ConcreteImpl, ImplDef, Plugin, SpecDef, impl, spec
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
- "ConcreteImpl",
6
- "Experiment",
7
- "ImplDef",
8
+ "ImplInfo",
9
+ "MethodName",
8
10
  "Plugin",
9
- "SpecDef",
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
@@ -1,177 +1,71 @@
1
1
  import functools
2
2
  import inspect
3
- from collections.abc import Callable, Generator, Iterable, Sequence
4
- from typing import Any, Self, overload, override
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.grapes.typed import Decorator
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
- @attrs.frozen
27
- class ImplDef:
28
- name: str
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(*, firstresult: bool = False) -> Decorator: ...
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](fn: C, /, *, firstresult: bool = False) -> C: ...
35
+ def spec[C: Callable](
36
+ *, delegate: bool = True, first_result: bool = False
37
+ ) -> Callable[[C], C]: ...
131
38
  def spec(
132
- fn: Callable | None = None, /, *, firstresult: bool = False
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
- name: str,
168
- after: str | Sequence[str] = (),
169
- before: str | Sequence[str] = (),
170
- priority: int = 0,
171
- ) -> Decorator | Callable:
172
- if fn is None:
173
- return functools.partial(
174
- impl, name=name, after=after, before=before, priority=priority
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
- fn.impl = ImplDef(name=name, after=after, before=before, priority=priority) # pyright: ignore[reportFunctionMemberAccess]
177
- return override(fn)
71
+ }
@@ -0,0 +1,2 @@
1
+ type MethodName = str
2
+ type PluginId = str
@@ -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 pathutils as _path
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
- _path.entrypoint(absolute=True), search_parent_directories=True
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(_path.entrypoint(absolute=True), search_parent_directories=True)
49
+ return git.Repo(paths.entrypoint(absolute=True), search_parent_directories=True)