liblaf-cherries 0.1.7__tar.gz → 0.2.1__tar.gz
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-0.1.7 → liblaf_cherries-0.2.1}/PKG-INFO +5 -3
- {liblaf_cherries-0.1.7 → liblaf_cherries-0.2.1}/pyproject.toml +5 -9
- liblaf_cherries-0.2.1/src/liblaf/cherries/__init__.pyi +50 -0
- liblaf_cherries-0.2.1/src/liblaf/cherries/_entrypoint.py +56 -0
- {liblaf_cherries-0.1.7 → liblaf_cherries-0.2.1}/src/liblaf/cherries/_version.py +2 -2
- {liblaf_cherries-0.1.7 → liblaf_cherries-0.2.1}/src/liblaf/cherries/config/_asset.py +3 -3
- liblaf_cherries-0.2.1/src/liblaf/cherries/core/__init__.pyi +20 -0
- liblaf_cherries-0.2.1/src/liblaf/cherries/core/_impl.py +58 -0
- liblaf_cherries-0.2.1/src/liblaf/cherries/core/_plugin.py +96 -0
- liblaf_cherries-0.2.1/src/liblaf/cherries/core/_run.py +42 -0
- liblaf_cherries-0.2.1/src/liblaf/cherries/core/_spec.py +71 -0
- liblaf_cherries-0.2.1/src/liblaf/cherries/core/typed.py +2 -0
- {liblaf_cherries-0.1.7 → liblaf_cherries-0.2.1}/src/liblaf/cherries/meta/_git.py +3 -3
- {liblaf_cherries-0.1.7 → liblaf_cherries-0.2.1}/src/liblaf/cherries/meta/_name.py +3 -3
- {liblaf_cherries-0.1.7/src/liblaf/cherries/pathutils → liblaf_cherries-0.2.1/src/liblaf/cherries/paths}/_path.py +9 -7
- liblaf_cherries-0.2.1/src/liblaf/cherries/plugins/__init__.pyi +3 -0
- liblaf_cherries-0.2.1/src/liblaf/cherries/plugins/logging.py +25 -0
- liblaf_cherries-0.2.1/src/liblaf/cherries/profiles/__init__.pyi +13 -0
- liblaf_cherries-0.2.1/src/liblaf/cherries/profiles/_abc.py +10 -0
- liblaf_cherries-0.2.1/src/liblaf/cherries/profiles/_default.py +12 -0
- liblaf_cherries-0.2.1/src/liblaf/cherries/profiles/_factory.py +21 -0
- liblaf_cherries-0.2.1/src/liblaf/cherries/profiles/_playground.py +13 -0
- liblaf_cherries-0.1.7/src/liblaf/cherries/__init__.pyi +0 -129
- liblaf_cherries-0.1.7/src/liblaf/cherries/_run.py +0 -51
- liblaf_cherries-0.1.7/src/liblaf/cherries/core/__init__.pyi +0 -12
- liblaf_cherries-0.1.7/src/liblaf/cherries/core/_exp.py +0 -119
- liblaf_cherries-0.1.7/src/liblaf/cherries/core/_spec.py +0 -177
- liblaf_cherries-0.1.7/src/liblaf/cherries/integration/__init__.pyi +0 -72
- liblaf_cherries-0.1.7/src/liblaf/cherries/integration/_abc.py +0 -144
- liblaf_cherries-0.1.7/src/liblaf/cherries/integration/_exp.py +0 -109
- liblaf_cherries-0.1.7/src/liblaf/cherries/integration/comet.py +0 -142
- liblaf_cherries-0.1.7/src/liblaf/cherries/integration/dvc.py +0 -51
- liblaf_cherries-0.1.7/src/liblaf/cherries/integration/git.py +0 -44
- liblaf_cherries-0.1.7/src/liblaf/cherries/integration/logging.py +0 -45
- liblaf_cherries-0.1.7/src/liblaf/cherries/presets/__init__.pyi +0 -5
- liblaf_cherries-0.1.7/src/liblaf/cherries/presets/_default.py +0 -46
- liblaf_cherries-0.1.7/src/liblaf/cherries/presets/_playground.py +0 -11
- liblaf_cherries-0.1.7/src/liblaf/cherries/presets/typed.py +0 -5
- {liblaf_cherries-0.1.7 → liblaf_cherries-0.2.1}/.gitignore +0 -0
- {liblaf_cherries-0.1.7 → liblaf_cherries-0.2.1}/LICENSE +0 -0
- {liblaf_cherries-0.1.7 → liblaf_cherries-0.2.1}/README.md +0 -0
- {liblaf_cherries-0.1.7 → liblaf_cherries-0.2.1}/src/liblaf/cherries/__init__.py +0 -0
- {liblaf_cherries-0.1.7 → liblaf_cherries-0.2.1}/src/liblaf/cherries/_version.pyi +0 -0
- {liblaf_cherries-0.1.7 → liblaf_cherries-0.2.1}/src/liblaf/cherries/config/__init__.py +0 -0
- {liblaf_cherries-0.1.7 → liblaf_cherries-0.2.1}/src/liblaf/cherries/config/__init__.pyi +0 -0
- {liblaf_cherries-0.1.7 → liblaf_cherries-0.2.1}/src/liblaf/cherries/config/_config.py +0 -0
- {liblaf_cherries-0.1.7 → liblaf_cherries-0.2.1}/src/liblaf/cherries/core/__init__.py +0 -0
- {liblaf_cherries-0.1.7/src/liblaf/cherries/integration → liblaf_cherries-0.2.1/src/liblaf/cherries/meta}/__init__.py +0 -0
- {liblaf_cherries-0.1.7 → liblaf_cherries-0.2.1}/src/liblaf/cherries/meta/__init__.pyi +0 -0
- {liblaf_cherries-0.1.7/src/liblaf/cherries/meta → liblaf_cherries-0.2.1/src/liblaf/cherries/paths}/__init__.py +0 -0
- {liblaf_cherries-0.1.7/src/liblaf/cherries/pathutils → liblaf_cherries-0.2.1/src/liblaf/cherries/paths}/__init__.pyi +0 -0
- {liblaf_cherries-0.1.7/src/liblaf/cherries/pathutils → liblaf_cherries-0.2.1/src/liblaf/cherries/paths}/_convert.py +0 -0
- {liblaf_cherries-0.1.7/src/liblaf/cherries/pathutils → liblaf_cherries-0.2.1/src/liblaf/cherries/paths}/_special.py +0 -0
- {liblaf_cherries-0.1.7/src/liblaf/cherries/pathutils → liblaf_cherries-0.2.1/src/liblaf/cherries/plugins}/__init__.py +0 -0
- {liblaf_cherries-0.1.7/src/liblaf/cherries/presets → liblaf_cherries-0.2.1/src/liblaf/cherries/profiles}/__init__.py +0 -0
- {liblaf_cherries-0.1.7 → liblaf_cherries-0.2.1}/src/liblaf/cherries/py.typed +0 -0
- {liblaf_cherries-0.1.7 → liblaf_cherries-0.2.1}/src/liblaf/cherries/typed.py +0 -0
- {liblaf_cherries-0.1.7 → liblaf_cherries-0.2.1}/src/liblaf/cherries/utils/__init__.py +0 -0
- {liblaf_cherries-0.1.7 → liblaf_cherries-0.2.1}/src/liblaf/cherries/utils/__init__.pyi +0 -0
- {liblaf_cherries-0.1.7 → liblaf_cherries-0.2.1}/src/liblaf/cherries/utils/_functools.py +0 -0
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: liblaf-cherries
|
3
|
-
Version: 0.1
|
3
|
+
Version: 0.2.1
|
4
4
|
Summary: Add your description here
|
5
5
|
Project-URL: Changelog, https://github.com/liblaf/cherries/blob/main/CHANGELOG.md
|
6
6
|
Project-URL: Documentation, https://liblaf.github.io/cherries/
|
@@ -31,17 +31,19 @@ Classifier: Topic :: System :: Logging
|
|
31
31
|
Classifier: Topic :: Utilities
|
32
32
|
Classifier: Typing :: Typed
|
33
33
|
Requires-Python: >=3.12
|
34
|
+
Requires-Dist: attrs<26,>=25.0.0
|
34
35
|
Requires-Dist: comet-ml<4,>=3.0.0
|
35
36
|
Requires-Dist: dvc[webdav]<4,>=3.0.0
|
36
37
|
Requires-Dist: environs<15,>=14.0.0
|
37
38
|
Requires-Dist: gitpython<4,>=3.0.0
|
38
|
-
Requires-Dist: lazy-loader<0.5,>=0.4
|
39
|
-
Requires-Dist: liblaf-grapes<
|
39
|
+
Requires-Dist: lazy-loader<0.5,>=0.4
|
40
|
+
Requires-Dist: liblaf-grapes<1.1,>=1
|
40
41
|
Requires-Dist: loguru<0.8,>=0.7.0
|
41
42
|
Requires-Dist: networkx<4,>=3.0.0
|
42
43
|
Requires-Dist: pydantic-settings<3,>=2.0.0
|
43
44
|
Requires-Dist: pydantic<3,>=2.0.0
|
44
45
|
Requires-Dist: rich<15,>=14.0.0
|
46
|
+
Requires-Dist: wrapt<2,>=1.0.0
|
45
47
|
Description-Content-Type: text/markdown
|
46
48
|
|
47
49
|
<div align="center" markdown><a name="readme-top"></a>
|
@@ -30,13 +30,7 @@ docs = [
|
|
30
30
|
"pymdown-extensions",
|
31
31
|
"ruff",
|
32
32
|
]
|
33
|
-
test = [
|
34
|
-
"pytest",
|
35
|
-
"pytest-benchmark",
|
36
|
-
"pytest-codspeed",
|
37
|
-
"pytest-cov",
|
38
|
-
"pytest-xdist",
|
39
|
-
]
|
33
|
+
test = ["pytest", "pytest-codspeed", "pytest-cov", "pytest-xdist"]
|
40
34
|
|
41
35
|
[project]
|
42
36
|
authors = [
|
@@ -64,17 +58,19 @@ classifiers = [
|
|
64
58
|
"Typing :: Typed",
|
65
59
|
]
|
66
60
|
dependencies = [
|
61
|
+
"attrs>=25.0.0,<26",
|
67
62
|
"comet-ml>=3.0.0,<4",
|
68
63
|
"dvc[webdav]>=3.0.0,<4",
|
69
64
|
"environs>=14.0.0,<15",
|
70
65
|
"gitpython>=3.0.0,<4",
|
71
|
-
"lazy-loader>=0.4
|
72
|
-
"liblaf-grapes>=
|
66
|
+
"lazy-loader>=0.4,<0.5",
|
67
|
+
"liblaf-grapes>=1,<1.1",
|
73
68
|
"loguru>=0.7.0,<0.8",
|
74
69
|
"networkx>=3.0.0,<4",
|
75
70
|
"pydantic-settings>=2.0.0,<3",
|
76
71
|
"pydantic>=2.0.0,<3",
|
77
72
|
"rich>=14.0.0,<15",
|
73
|
+
"wrapt>=1.0.0,<2",
|
78
74
|
]
|
79
75
|
description = "Add your description here"
|
80
76
|
dynamic = ["version"]
|
@@ -0,0 +1,50 @@
|
|
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 (
|
6
|
+
as_os_path,
|
7
|
+
as_path,
|
8
|
+
as_posix,
|
9
|
+
data,
|
10
|
+
entrypoint,
|
11
|
+
exp_dir,
|
12
|
+
git_root,
|
13
|
+
git_root_safe,
|
14
|
+
inputs,
|
15
|
+
outputs,
|
16
|
+
params,
|
17
|
+
path,
|
18
|
+
src,
|
19
|
+
)
|
20
|
+
|
21
|
+
__all__ = [
|
22
|
+
"BaseConfig",
|
23
|
+
"Plugin",
|
24
|
+
"Run",
|
25
|
+
"active_run",
|
26
|
+
"as_os_path",
|
27
|
+
"as_path",
|
28
|
+
"as_posix",
|
29
|
+
"config",
|
30
|
+
"core",
|
31
|
+
"data",
|
32
|
+
"end",
|
33
|
+
"entrypoint",
|
34
|
+
"exp_dir",
|
35
|
+
"git_root",
|
36
|
+
"git_root_safe",
|
37
|
+
"input",
|
38
|
+
"inputs",
|
39
|
+
"log_asset",
|
40
|
+
"log_metrics",
|
41
|
+
"meta",
|
42
|
+
"output",
|
43
|
+
"outputs",
|
44
|
+
"params",
|
45
|
+
"path",
|
46
|
+
"paths",
|
47
|
+
"run",
|
48
|
+
"src",
|
49
|
+
"start",
|
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
|
@@ -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]
|
@@ -0,0 +1,20 @@
|
|
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
|
6
|
+
|
7
|
+
__all__ = [
|
8
|
+
"ImplInfo",
|
9
|
+
"MethodName",
|
10
|
+
"Plugin",
|
11
|
+
"PluginId",
|
12
|
+
"Run",
|
13
|
+
"SpecInfo",
|
14
|
+
"active_run",
|
15
|
+
"get_impl_info",
|
16
|
+
"impl",
|
17
|
+
"log_asset",
|
18
|
+
"log_metrics",
|
19
|
+
"spec",
|
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
|
@@ -0,0 +1,71 @@
|
|
1
|
+
import functools
|
2
|
+
import inspect
|
3
|
+
from collections.abc import Callable, Mapping, Sequence
|
4
|
+
from typing import Any, Protocol, overload
|
5
|
+
|
6
|
+
import attrs
|
7
|
+
|
8
|
+
from liblaf import grapes
|
9
|
+
|
10
|
+
from .typed import MethodName
|
11
|
+
|
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: ...
|
22
|
+
|
23
|
+
|
24
|
+
@attrs.define
|
25
|
+
class SpecInfo:
|
26
|
+
delegate: bool = attrs.field(default=True)
|
27
|
+
first_result: bool = attrs.field(default=False)
|
28
|
+
|
29
|
+
|
30
|
+
@overload
|
31
|
+
def spec[C: Callable](
|
32
|
+
func: C, /, *, delegate: bool = True, first_result: bool = False
|
33
|
+
) -> C: ...
|
34
|
+
@overload
|
35
|
+
def spec[C: Callable](
|
36
|
+
*, delegate: bool = True, first_result: bool = False
|
37
|
+
) -> Callable[[C], C]: ...
|
38
|
+
def spec(
|
39
|
+
func: Callable | None = None,
|
40
|
+
/,
|
41
|
+
*,
|
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
|
70
|
+
)
|
71
|
+
}
|
@@ -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)
|
@@ -3,7 +3,7 @@ from pathlib import Path
|
|
3
3
|
import git.exc
|
4
4
|
|
5
5
|
from liblaf import grapes
|
6
|
-
from liblaf.cherries import
|
6
|
+
from liblaf.cherries import paths
|
7
7
|
|
8
8
|
from ._git import git_info
|
9
9
|
|
@@ -18,8 +18,8 @@ def project_name() -> str:
|
|
18
18
|
|
19
19
|
|
20
20
|
def exp_name() -> str:
|
21
|
-
exp_dir: Path =
|
22
|
-
exp_name: str =
|
21
|
+
exp_dir: Path = paths.entrypoint(absolute=False)
|
22
|
+
exp_name: str = paths.as_posix(exp_dir)
|
23
23
|
exp_name = exp_name.removeprefix("exp")
|
24
24
|
exp_name = exp_name.removeprefix("/")
|
25
25
|
return exp_name
|
@@ -1,4 +1,5 @@
|
|
1
1
|
import sys
|
2
|
+
from collections.abc import Container
|
2
3
|
from pathlib import Path
|
3
4
|
|
4
5
|
import git
|
@@ -27,7 +28,7 @@ def git_root_safe() -> Path:
|
|
27
28
|
try:
|
28
29
|
return git_root()
|
29
30
|
except git.exc.InvalidGitRepositoryError:
|
30
|
-
logger.warning("Not in a git repository, using current directory")
|
31
|
+
logger.warning("Not in a git repository, using current directory", once=True)
|
31
32
|
return _entrypoint_absolute().parent
|
32
33
|
|
33
34
|
|
@@ -50,15 +51,16 @@ def _entrypoint_relative() -> Path:
|
|
50
51
|
return path.relative_to(git_root_safe())
|
51
52
|
|
52
53
|
|
54
|
+
EXP_DIR_NAMES: Container[str] = {"exp", "experiment", "experiments", "exps", "src"}
|
55
|
+
|
56
|
+
|
53
57
|
@utils.cache
|
54
58
|
def _exp_dir_absolute() -> Path:
|
55
59
|
entrypoint: Path = _entrypoint_absolute()
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
return path
|
61
|
-
return git_root_safe()
|
60
|
+
parent: Path = entrypoint.parent
|
61
|
+
if parent.name in EXP_DIR_NAMES:
|
62
|
+
return parent.parent
|
63
|
+
return parent
|
62
64
|
|
63
65
|
|
64
66
|
@utils.cache
|
@@ -0,0 +1,25 @@
|
|
1
|
+
from typing import override
|
2
|
+
|
3
|
+
from liblaf import grapes
|
4
|
+
from liblaf.cherries import core
|
5
|
+
|
6
|
+
|
7
|
+
class Logging(core.Run):
|
8
|
+
@override
|
9
|
+
@core.impl
|
10
|
+
def start(self, *args, **kwargs) -> None:
|
11
|
+
profile = grapes.logging.profiles.ProfileCherries(
|
12
|
+
handlers=[
|
13
|
+
grapes.logging.rich_handler(),
|
14
|
+
grapes.logging.file_handler(sink=self.plugin_root.exp_dir / "run.log"),
|
15
|
+
]
|
16
|
+
)
|
17
|
+
grapes.logging.init(profile=profile)
|
18
|
+
|
19
|
+
@override
|
20
|
+
@core.impl
|
21
|
+
def end(self, *args, **kwargs) -> None:
|
22
|
+
if (self.plugin_root.exp_dir / "run.log").exists():
|
23
|
+
self.plugin_root.log_asset(self.plugin_root.exp_dir / "run.log")
|
24
|
+
if (self.plugin_root.exp_dir / "run.log.jsonl").exists():
|
25
|
+
self.plugin_root.log_asset(self.plugin_root.exp_dir / "run.log.jsonl")
|
@@ -0,0 +1,13 @@
|
|
1
|
+
from ._abc import Profile
|
2
|
+
from ._default import ProfileDefault
|
3
|
+
from ._factory import ProfileLike, ProfileName, factory
|
4
|
+
from ._playground import ProfilePlayground
|
5
|
+
|
6
|
+
__all__ = [
|
7
|
+
"Profile",
|
8
|
+
"ProfileDefault",
|
9
|
+
"ProfileLike",
|
10
|
+
"ProfileName",
|
11
|
+
"ProfilePlayground",
|
12
|
+
"factory",
|
13
|
+
]
|
@@ -0,0 +1,12 @@
|
|
1
|
+
from typing import override
|
2
|
+
|
3
|
+
from liblaf.cherries import core
|
4
|
+
|
5
|
+
from ._playground import ProfilePlayground
|
6
|
+
|
7
|
+
|
8
|
+
class ProfileDefault(ProfilePlayground):
|
9
|
+
@override # impl Profile
|
10
|
+
def init(self) -> core.Run:
|
11
|
+
run: core.Run = super().init()
|
12
|
+
return run
|
@@ -0,0 +1,21 @@
|
|
1
|
+
from typing import Literal
|
2
|
+
|
3
|
+
from ._abc import Profile
|
4
|
+
|
5
|
+
# ensure profiles are registered
|
6
|
+
from ._default import ProfileDefault
|
7
|
+
from ._playground import ProfilePlayground # noqa: F401
|
8
|
+
|
9
|
+
# for code-completion
|
10
|
+
type ProfileName = Literal["default", "playground"] | str # noqa: PYI051
|
11
|
+
type ProfileLike = ProfileName | Profile | type[Profile]
|
12
|
+
|
13
|
+
|
14
|
+
def factory(profile: ProfileLike | None = None) -> Profile:
|
15
|
+
if profile is None:
|
16
|
+
return ProfileDefault()
|
17
|
+
if isinstance(profile, str):
|
18
|
+
return Profile[profile]()
|
19
|
+
if isinstance(profile, Profile):
|
20
|
+
return profile
|
21
|
+
return profile()
|