liblaf-cherries 0.4.3__tar.gz → 0.5.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.4.3 → liblaf_cherries-0.5.1}/PKG-INFO +2 -2
- {liblaf_cherries-0.4.3 → liblaf_cherries-0.5.1}/pyproject.toml +1 -1
- {liblaf_cherries-0.4.3 → liblaf_cherries-0.5.1}/src/liblaf/cherries/__init__.py +1 -0
- {liblaf_cherries-0.4.3 → liblaf_cherries-0.5.1}/src/liblaf/cherries/__init__.pyi +5 -7
- {liblaf_cherries-0.4.3 → liblaf_cherries-0.5.1}/src/liblaf/cherries/_entrypoint.py +11 -10
- {liblaf_cherries-0.4.3 → liblaf_cherries-0.5.1}/src/liblaf/cherries/_version.py +2 -2
- {liblaf_cherries-0.4.3 → liblaf_cherries-0.5.1}/src/liblaf/cherries/config/__init__.py +1 -0
- {liblaf_cherries-0.4.3 → liblaf_cherries-0.5.1}/src/liblaf/cherries/config/__init__.pyi +16 -4
- liblaf_cherries-0.5.1/src/liblaf/cherries/config/_config.py +5 -0
- {liblaf_cherries-0.4.3/src/liblaf/cherries/meta → liblaf_cherries-0.5.1/src/liblaf/cherries/config/asset}/__init__.py +1 -0
- liblaf_cherries-0.5.1/src/liblaf/cherries/config/asset/__init__.pyi +34 -0
- liblaf_cherries-0.5.1/src/liblaf/cherries/config/asset/_meta.py +98 -0
- liblaf_cherries-0.5.1/src/liblaf/cherries/config/asset/_registry.py +25 -0
- {liblaf_cherries-0.4.3/src/liblaf/cherries/core → liblaf_cherries-0.5.1/src/liblaf/cherries/config/asset/resolvers}/__init__.py +1 -0
- liblaf_cherries-0.5.1/src/liblaf/cherries/config/asset/resolvers/__init__.pyi +5 -0
- liblaf_cherries-0.5.1/src/liblaf/cherries/config/asset/resolvers/_abc.py +17 -0
- liblaf_cherries-0.5.1/src/liblaf/cherries/config/asset/resolvers/_series.py +18 -0
- liblaf_cherries-0.5.1/src/liblaf/cherries/config/asset/resolvers/_vtk.py +33 -0
- liblaf_cherries-0.5.1/src/liblaf/cherries/core/__init__.py +4 -0
- {liblaf_cherries-0.4.3 → liblaf_cherries-0.5.1}/src/liblaf/cherries/core/__init__.pyi +15 -3
- {liblaf_cherries-0.4.3 → liblaf_cherries-0.5.1}/src/liblaf/cherries/core/_impl.py +20 -7
- liblaf_cherries-0.5.1/src/liblaf/cherries/core/_plugin.py +99 -0
- {liblaf_cherries-0.4.3 → liblaf_cherries-0.5.1}/src/liblaf/cherries/core/_run.py +22 -52
- {liblaf_cherries-0.4.3 → liblaf_cherries-0.5.1}/src/liblaf/cherries/core/_spec.py +10 -11
- liblaf_cherries-0.5.1/src/liblaf/cherries/core/_utils.py +63 -0
- liblaf_cherries-0.5.1/src/liblaf/cherries/meta/__init__.py +4 -0
- {liblaf_cherries-0.4.3 → liblaf_cherries-0.5.1}/src/liblaf/cherries/meta/_git.py +3 -3
- {liblaf_cherries-0.4.3 → liblaf_cherries-0.5.1}/src/liblaf/cherries/meta/_name.py +3 -3
- liblaf_cherries-0.5.1/src/liblaf/cherries/path_utils/__init__.py +4 -0
- {liblaf_cherries-0.4.3/src/liblaf/cherries/paths → liblaf_cherries-0.5.1/src/liblaf/cherries/path_utils}/__init__.pyi +2 -3
- {liblaf_cherries-0.4.3/src/liblaf/cherries/paths → liblaf_cherries-0.5.1/src/liblaf/cherries/path_utils}/_convert.py +1 -1
- liblaf_cherries-0.5.1/src/liblaf/cherries/path_utils/_path.py +43 -0
- liblaf_cherries-0.5.1/src/liblaf/cherries/plugins/__init__.py +4 -0
- {liblaf_cherries-0.4.3 → liblaf_cherries-0.5.1}/src/liblaf/cherries/plugins/comet.py +16 -9
- {liblaf_cherries-0.4.3 → liblaf_cherries-0.5.1}/src/liblaf/cherries/plugins/dvc.py +1 -1
- {liblaf_cherries-0.4.3 → liblaf_cherries-0.5.1}/src/liblaf/cherries/plugins/git_.py +17 -21
- {liblaf_cherries-0.4.3 → liblaf_cherries-0.5.1}/src/liblaf/cherries/plugins/local.py +3 -3
- {liblaf_cherries-0.4.3 → liblaf_cherries-0.5.1}/src/liblaf/cherries/plugins/logging.py +2 -2
- liblaf_cherries-0.5.1/src/liblaf/cherries/profiles/__init__.py +4 -0
- {liblaf_cherries-0.4.3 → liblaf_cherries-0.5.1}/src/liblaf/cherries/profiles/_factory.py +2 -3
- liblaf_cherries-0.5.1/src/liblaf/cherries/utils/__init__.py +4 -0
- liblaf_cherries-0.4.3/src/liblaf/cherries/config/_asset.py +0 -92
- liblaf_cherries-0.4.3/src/liblaf/cherries/config/_config.py +0 -6
- liblaf_cherries-0.4.3/src/liblaf/cherries/core/_plugin.py +0 -96
- liblaf_cherries-0.4.3/src/liblaf/cherries/core/_utils.py +0 -20
- liblaf_cherries-0.4.3/src/liblaf/cherries/paths/__init__.py +0 -3
- liblaf_cherries-0.4.3/src/liblaf/cherries/paths/_path.py +0 -70
- liblaf_cherries-0.4.3/src/liblaf/cherries/plugins/__init__.py +0 -3
- liblaf_cherries-0.4.3/src/liblaf/cherries/profiles/__init__.py +0 -3
- liblaf_cherries-0.4.3/src/liblaf/cherries/utils/__init__.py +0 -3
- {liblaf_cherries-0.4.3 → liblaf_cherries-0.5.1}/.gitignore +0 -0
- {liblaf_cherries-0.4.3 → liblaf_cherries-0.5.1}/LICENSE +0 -0
- {liblaf_cherries-0.4.3 → liblaf_cherries-0.5.1}/README.md +0 -0
- {liblaf_cherries-0.4.3 → liblaf_cherries-0.5.1}/src/liblaf/cherries/_version.pyi +0 -0
- /liblaf_cherries-0.4.3/src/liblaf/cherries/core/typed.py → /liblaf_cherries-0.5.1/src/liblaf/cherries/core/typing.py +0 -0
- {liblaf_cherries-0.4.3 → liblaf_cherries-0.5.1}/src/liblaf/cherries/meta/__init__.pyi +0 -0
- {liblaf_cherries-0.4.3/src/liblaf/cherries/paths → liblaf_cherries-0.5.1/src/liblaf/cherries/path_utils}/_special.py +0 -0
- {liblaf_cherries-0.4.3 → liblaf_cherries-0.5.1}/src/liblaf/cherries/plugins/__init__.pyi +0 -0
- {liblaf_cherries-0.4.3 → liblaf_cherries-0.5.1}/src/liblaf/cherries/profiles/__init__.pyi +0 -0
- {liblaf_cherries-0.4.3 → liblaf_cherries-0.5.1}/src/liblaf/cherries/profiles/_abc.py +0 -0
- {liblaf_cherries-0.4.3 → liblaf_cherries-0.5.1}/src/liblaf/cherries/profiles/_default.py +0 -0
- {liblaf_cherries-0.4.3 → liblaf_cherries-0.5.1}/src/liblaf/cherries/profiles/_playground.py +0 -0
- {liblaf_cherries-0.4.3 → liblaf_cherries-0.5.1}/src/liblaf/cherries/py.typed +0 -0
- /liblaf_cherries-0.4.3/src/liblaf/cherries/typed.py → /liblaf_cherries-0.5.1/src/liblaf/cherries/typing.py +0 -0
- {liblaf_cherries-0.4.3 → liblaf_cherries-0.5.1}/src/liblaf/cherries/utils/__init__.pyi +0 -0
- {liblaf_cherries-0.4.3 → liblaf_cherries-0.5.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.
|
3
|
+
Version: 0.5.1
|
4
4
|
Summary: 🍒 Sweet experiment tracking with Comet, DVC, and Git integration.
|
5
5
|
Project-URL: Changelog, https://github.com/liblaf/cherries/blob/main/CHANGELOG.md
|
6
6
|
Project-URL: Documentation, https://cherries.readthedocs.io/
|
@@ -37,7 +37,7 @@ Requires-Dist: dvc[all]<4,>=3
|
|
37
37
|
Requires-Dist: environs<15,>=14
|
38
38
|
Requires-Dist: gitpython<4,>=3
|
39
39
|
Requires-Dist: lazy-loader<0.5,>=0.4
|
40
|
-
Requires-Dist: liblaf-grapes<5
|
40
|
+
Requires-Dist: liblaf-grapes<6,>=5
|
41
41
|
Requires-Dist: loguru<0.8,>=0.7
|
42
42
|
Requires-Dist: networkx<4,>=3
|
43
43
|
Requires-Dist: pydantic-settings<3,>=2
|
@@ -1,4 +1,4 @@
|
|
1
|
-
from . import config, core, meta,
|
1
|
+
from . import config, core, meta, path_utils, plugins
|
2
2
|
from ._entrypoint import end, run, start
|
3
3
|
from .config import BaseConfig, input, output # noqa: A004
|
4
4
|
from .core import (
|
@@ -15,17 +15,16 @@ from .core import (
|
|
15
15
|
log_parameter,
|
16
16
|
log_parameters,
|
17
17
|
)
|
18
|
-
from .
|
18
|
+
from .path_utils import (
|
19
19
|
as_os_path,
|
20
20
|
as_path,
|
21
21
|
as_posix,
|
22
22
|
data,
|
23
23
|
entrypoint,
|
24
24
|
exp_dir,
|
25
|
-
git_root,
|
26
|
-
git_root_safe,
|
27
25
|
params,
|
28
26
|
path,
|
27
|
+
project_dir,
|
29
28
|
src,
|
30
29
|
)
|
31
30
|
|
@@ -43,8 +42,6 @@ __all__ = [
|
|
43
42
|
"end",
|
44
43
|
"entrypoint",
|
45
44
|
"exp_dir",
|
46
|
-
"git_root",
|
47
|
-
"git_root_safe",
|
48
45
|
"input",
|
49
46
|
"log_asset",
|
50
47
|
"log_input",
|
@@ -59,8 +56,9 @@ __all__ = [
|
|
59
56
|
"output",
|
60
57
|
"params",
|
61
58
|
"path",
|
62
|
-
"
|
59
|
+
"path_utils",
|
63
60
|
"plugins",
|
61
|
+
"project_dir",
|
64
62
|
"run",
|
65
63
|
"src",
|
66
64
|
"start",
|
@@ -9,8 +9,8 @@ from liblaf.cherries import config as _config
|
|
9
9
|
from liblaf.cherries import core, profiles
|
10
10
|
|
11
11
|
|
12
|
-
def end() -> None:
|
13
|
-
core.active_run.end()
|
12
|
+
def end(*args, **kwargs) -> None:
|
13
|
+
core.active_run.end(*args, **kwargs)
|
14
14
|
|
15
15
|
|
16
16
|
def run[T](main: Callable[..., T], *, profile: profiles.ProfileLike | None = None) -> T:
|
@@ -27,12 +27,13 @@ def run[T](main: Callable[..., T], *, profile: profiles.ProfileLike | None = Non
|
|
27
27
|
run.log_parameters(_config.model_dump_without_assets(config, mode="json"))
|
28
28
|
for path in _config.get_inputs(config):
|
29
29
|
run.log_input(path)
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
30
|
+
try:
|
31
|
+
return main(*args, **kwargs)
|
32
|
+
finally:
|
33
|
+
for config in configs:
|
34
|
+
for path in _config.get_outputs(config):
|
35
|
+
run.log_output(path)
|
36
|
+
run.end()
|
36
37
|
|
37
38
|
|
38
39
|
def start(
|
@@ -40,8 +41,8 @@ def start(
|
|
40
41
|
) -> core.Run:
|
41
42
|
run: core.Run = profiles.factory(profile).init()
|
42
43
|
run.start()
|
43
|
-
run.log_other("cherries.entrypoint", run.entrypoint.relative_to(run.
|
44
|
-
run.log_other("cherries.exp_dir", run.exp_dir.relative_to(run.
|
44
|
+
run.log_other("cherries.entrypoint", run.entrypoint.relative_to(run.project_dir))
|
45
|
+
run.log_other("cherries.exp_dir", run.exp_dir.relative_to(run.project_dir))
|
45
46
|
run.log_other("cherries.start_time", run.start_time)
|
46
47
|
return run
|
47
48
|
|
@@ -28,7 +28,7 @@ version_tuple: VERSION_TUPLE
|
|
28
28
|
commit_id: COMMIT_ID
|
29
29
|
__commit_id__: COMMIT_ID
|
30
30
|
|
31
|
-
__version__ = version = '0.
|
32
|
-
__version_tuple__ = version_tuple = (0,
|
31
|
+
__version__ = version = '0.5.1'
|
32
|
+
__version_tuple__ = version_tuple = (0, 5, 1)
|
33
33
|
|
34
34
|
__commit_id__ = commit_id = None
|
@@ -1,7 +1,14 @@
|
|
1
|
-
from .
|
1
|
+
from ._config import BaseConfig
|
2
|
+
from .asset import (
|
2
3
|
AssetKind,
|
4
|
+
AssetResolver,
|
5
|
+
AssetResolverRegistry,
|
6
|
+
AssetResolverSeries,
|
7
|
+
AssetResolverVtk,
|
8
|
+
Extra,
|
3
9
|
MetaAsset,
|
4
|
-
|
10
|
+
asset,
|
11
|
+
asset_resolver_registry,
|
5
12
|
get_assets,
|
6
13
|
get_inputs,
|
7
14
|
get_outputs,
|
@@ -9,13 +16,18 @@ from ._asset import (
|
|
9
16
|
model_dump_without_assets,
|
10
17
|
output,
|
11
18
|
)
|
12
|
-
from ._config import BaseConfig
|
13
19
|
|
14
20
|
__all__ = [
|
15
21
|
"AssetKind",
|
22
|
+
"AssetResolver",
|
23
|
+
"AssetResolverRegistry",
|
24
|
+
"AssetResolverSeries",
|
25
|
+
"AssetResolverVtk",
|
16
26
|
"BaseConfig",
|
27
|
+
"Extra",
|
17
28
|
"MetaAsset",
|
18
|
-
"
|
29
|
+
"asset",
|
30
|
+
"asset_resolver_registry",
|
19
31
|
"get_assets",
|
20
32
|
"get_inputs",
|
21
33
|
"get_outputs",
|
@@ -0,0 +1,34 @@
|
|
1
|
+
from . import resolvers
|
2
|
+
from ._meta import (
|
3
|
+
AssetKind,
|
4
|
+
Extra,
|
5
|
+
MetaAsset,
|
6
|
+
asset,
|
7
|
+
get_assets,
|
8
|
+
get_inputs,
|
9
|
+
get_outputs,
|
10
|
+
input, # noqa: A004
|
11
|
+
model_dump_without_assets,
|
12
|
+
output,
|
13
|
+
)
|
14
|
+
from ._registry import AssetResolverRegistry, asset_resolver_registry
|
15
|
+
from .resolvers import AssetResolver, AssetResolverSeries, AssetResolverVtk
|
16
|
+
|
17
|
+
__all__ = [
|
18
|
+
"AssetKind",
|
19
|
+
"AssetResolver",
|
20
|
+
"AssetResolverRegistry",
|
21
|
+
"AssetResolverSeries",
|
22
|
+
"AssetResolverVtk",
|
23
|
+
"Extra",
|
24
|
+
"MetaAsset",
|
25
|
+
"asset",
|
26
|
+
"asset_resolver_registry",
|
27
|
+
"get_assets",
|
28
|
+
"get_inputs",
|
29
|
+
"get_outputs",
|
30
|
+
"input",
|
31
|
+
"model_dump_without_assets",
|
32
|
+
"output",
|
33
|
+
"resolvers",
|
34
|
+
]
|
@@ -0,0 +1,98 @@
|
|
1
|
+
import enum
|
2
|
+
import os
|
3
|
+
from collections.abc import Callable, Generator, Iterable
|
4
|
+
from pathlib import Path
|
5
|
+
from typing import Any, Literal
|
6
|
+
|
7
|
+
import attrs
|
8
|
+
import pydantic
|
9
|
+
|
10
|
+
import liblaf.cherries.path_utils as pu
|
11
|
+
from liblaf import grapes
|
12
|
+
from liblaf.cherries.typing import PathLike
|
13
|
+
|
14
|
+
from ._registry import asset_resolver_registry
|
15
|
+
|
16
|
+
type Extra = (
|
17
|
+
PathLike
|
18
|
+
| Iterable[PathLike]
|
19
|
+
| Callable[[PathLike], PathLike | Iterable[PathLike | None] | None]
|
20
|
+
| None
|
21
|
+
)
|
22
|
+
|
23
|
+
|
24
|
+
class AssetKind(enum.StrEnum):
|
25
|
+
INPUT = enum.auto()
|
26
|
+
OUTPUT = enum.auto()
|
27
|
+
|
28
|
+
|
29
|
+
@attrs.define
|
30
|
+
class MetaAsset:
|
31
|
+
kind: AssetKind
|
32
|
+
extra: Extra = None
|
33
|
+
|
34
|
+
def resolve(self, value: Path) -> Generator[Path]:
|
35
|
+
if self.extra is None:
|
36
|
+
yield from asset_resolver_registry.resolve(value)
|
37
|
+
return
|
38
|
+
extra: PathLike | Iterable[PathLike | None] | None = (
|
39
|
+
self.extra(value) if callable(self.extra) else self.extra # noqa: S610
|
40
|
+
)
|
41
|
+
for p in grapes.as_iterable(extra, base_type=(str, bytes, os.PathLike)):
|
42
|
+
if p is None:
|
43
|
+
continue
|
44
|
+
yield Path(p)
|
45
|
+
|
46
|
+
|
47
|
+
def asset(path: PathLike, extra: Extra = None, *, kind: AssetKind, **kwargs) -> Path:
|
48
|
+
field_info: pydantic.fields.FieldInfo = pydantic.Field(pu.data(path), **kwargs) # pyright: ignore[reportAssignmentType]
|
49
|
+
field_info.metadata.append(MetaAsset(kind=kind, extra=extra))
|
50
|
+
return field_info # pyright: ignore[reportReturnType]
|
51
|
+
|
52
|
+
|
53
|
+
def get_assets(cfg: pydantic.BaseModel, kind: AssetKind) -> Generator[Path]:
|
54
|
+
for name, info in type(cfg).model_fields.items():
|
55
|
+
value: Any = getattr(cfg, name)
|
56
|
+
if isinstance(value, pydantic.BaseModel):
|
57
|
+
yield from get_assets(value, kind)
|
58
|
+
for meta in info.metadata:
|
59
|
+
if isinstance(meta, MetaAsset) and meta.kind == kind:
|
60
|
+
value: Path = Path(value)
|
61
|
+
yield value
|
62
|
+
yield from meta.resolve(value)
|
63
|
+
|
64
|
+
|
65
|
+
def get_inputs(cfg: pydantic.BaseModel) -> Generator[Path]:
|
66
|
+
yield from get_assets(cfg, AssetKind.INPUT)
|
67
|
+
|
68
|
+
|
69
|
+
def get_outputs(cfg: pydantic.BaseModel) -> Generator[Path]:
|
70
|
+
yield from get_assets(cfg, AssetKind.OUTPUT)
|
71
|
+
|
72
|
+
|
73
|
+
def input(path: PathLike, extra: Extra = None, **kwargs) -> Path: # noqa: A001
|
74
|
+
return asset(path, extra=extra, kind=AssetKind.INPUT, **kwargs)
|
75
|
+
|
76
|
+
|
77
|
+
def model_dump_without_assets(
|
78
|
+
model: pydantic.BaseModel,
|
79
|
+
*,
|
80
|
+
mode: str | Literal["json", "python"] = "json", # noqa: PYI051
|
81
|
+
**kwargs,
|
82
|
+
) -> dict[str, Any]:
|
83
|
+
data: dict[str, Any] = model.model_dump(mode=mode, **kwargs)
|
84
|
+
for name, info in type(model).model_fields.items():
|
85
|
+
value: Any = getattr(model, name)
|
86
|
+
if isinstance(value, pydantic.BaseModel):
|
87
|
+
value = model_dump_without_assets(value)
|
88
|
+
for meta in info.metadata:
|
89
|
+
if isinstance(meta, MetaAsset):
|
90
|
+
del data[name]
|
91
|
+
break
|
92
|
+
else:
|
93
|
+
data[name] = value
|
94
|
+
return data
|
95
|
+
|
96
|
+
|
97
|
+
def output(path: PathLike, extra: Extra = None, **kwargs) -> Path:
|
98
|
+
return asset(path, extra=extra, kind=AssetKind.OUTPUT, **kwargs)
|
@@ -0,0 +1,25 @@
|
|
1
|
+
from collections.abc import Generator
|
2
|
+
from pathlib import Path
|
3
|
+
|
4
|
+
import attrs
|
5
|
+
|
6
|
+
from .resolvers import AssetResolver, AssetResolverSeries, AssetResolverVtk
|
7
|
+
|
8
|
+
|
9
|
+
@attrs.define
|
10
|
+
class AssetResolverRegistry:
|
11
|
+
_registry: dict[str, AssetResolver] = attrs.field(factory=dict)
|
12
|
+
|
13
|
+
def register(self, resolver: AssetResolver) -> None:
|
14
|
+
self._registry[resolver.id] = resolver
|
15
|
+
|
16
|
+
def resolve(self, path: Path) -> Generator[Path]:
|
17
|
+
for resolver in self._registry.values():
|
18
|
+
if not resolver.match(path):
|
19
|
+
continue
|
20
|
+
yield from resolver.resolve(path)
|
21
|
+
|
22
|
+
|
23
|
+
asset_resolver_registry: AssetResolverRegistry = AssetResolverRegistry()
|
24
|
+
asset_resolver_registry.register(AssetResolverSeries())
|
25
|
+
asset_resolver_registry.register(AssetResolverVtk())
|
@@ -0,0 +1,17 @@
|
|
1
|
+
import abc
|
2
|
+
from collections.abc import Iterable
|
3
|
+
from pathlib import Path
|
4
|
+
|
5
|
+
|
6
|
+
class AssetResolver:
|
7
|
+
@property
|
8
|
+
def id(self) -> str:
|
9
|
+
return type(self).__name__
|
10
|
+
|
11
|
+
@abc.abstractmethod
|
12
|
+
def match(self, path: Path) -> bool:
|
13
|
+
raise NotImplementedError
|
14
|
+
|
15
|
+
@abc.abstractmethod
|
16
|
+
def resolve(self, path: Path) -> Iterable[Path]:
|
17
|
+
raise NotImplementedError
|
@@ -0,0 +1,18 @@
|
|
1
|
+
from collections.abc import Generator
|
2
|
+
from pathlib import Path
|
3
|
+
from typing import override
|
4
|
+
|
5
|
+
from ._abc import AssetResolver
|
6
|
+
|
7
|
+
|
8
|
+
class AssetResolverSeries(AssetResolver):
|
9
|
+
@override
|
10
|
+
def match(self, path: Path) -> bool:
|
11
|
+
return path.suffix == ".series"
|
12
|
+
|
13
|
+
@override
|
14
|
+
def resolve(self, path: Path) -> Generator[Path]:
|
15
|
+
if (folder := path.with_suffix(".d")).exists():
|
16
|
+
yield folder
|
17
|
+
if (folder := path.with_suffix("")).exists():
|
18
|
+
yield folder
|
@@ -0,0 +1,33 @@
|
|
1
|
+
from collections.abc import Generator
|
2
|
+
from pathlib import Path
|
3
|
+
from typing import override
|
4
|
+
|
5
|
+
import attrs
|
6
|
+
|
7
|
+
from ._abc import AssetResolver
|
8
|
+
|
9
|
+
|
10
|
+
@attrs.define
|
11
|
+
class AssetResolverVtk(AssetResolver):
|
12
|
+
SUFFIXES: set[str] = attrs.field(
|
13
|
+
factory=lambda: {
|
14
|
+
".obj",
|
15
|
+
".ply",
|
16
|
+
".stl",
|
17
|
+
".vti",
|
18
|
+
".vtk",
|
19
|
+
".vtp",
|
20
|
+
".vtr",
|
21
|
+
".vts",
|
22
|
+
".vtu",
|
23
|
+
}
|
24
|
+
)
|
25
|
+
|
26
|
+
@override
|
27
|
+
def match(self, path: Path) -> bool:
|
28
|
+
return path.suffix in self.SUFFIXES
|
29
|
+
|
30
|
+
@override
|
31
|
+
def resolve(self, path: Path) -> Generator[Path]:
|
32
|
+
if (landmarks := path.with_suffix(".landmarks.json")).exists():
|
33
|
+
yield landmarks
|
@@ -1,4 +1,4 @@
|
|
1
|
-
from ._impl import ImplInfo, get_impl_info, impl
|
1
|
+
from ._impl import ImplInfo, collect_impls, get_impl_info, impl
|
2
2
|
from ._plugin import Plugin
|
3
3
|
from ._run import (
|
4
4
|
Run,
|
@@ -15,17 +15,27 @@ from ._run import (
|
|
15
15
|
log_parameters,
|
16
16
|
start,
|
17
17
|
)
|
18
|
-
from ._spec import SpecInfo, spec
|
19
|
-
from .
|
18
|
+
from ._spec import SpecInfo, collect_specs, spec
|
19
|
+
from ._utils import (
|
20
|
+
PluginCachedProperty,
|
21
|
+
PluginProperty,
|
22
|
+
plugin_cached_property,
|
23
|
+
plugin_property,
|
24
|
+
)
|
25
|
+
from .typing import MethodName, PluginId
|
20
26
|
|
21
27
|
__all__ = [
|
22
28
|
"ImplInfo",
|
23
29
|
"MethodName",
|
24
30
|
"Plugin",
|
31
|
+
"PluginCachedProperty",
|
25
32
|
"PluginId",
|
33
|
+
"PluginProperty",
|
26
34
|
"Run",
|
27
35
|
"SpecInfo",
|
28
36
|
"active_run",
|
37
|
+
"collect_impls",
|
38
|
+
"collect_specs",
|
29
39
|
"end",
|
30
40
|
"get_impl_info",
|
31
41
|
"impl",
|
@@ -38,6 +48,8 @@ __all__ = [
|
|
38
48
|
"log_output",
|
39
49
|
"log_parameter",
|
40
50
|
"log_parameters",
|
51
|
+
"plugin_cached_property",
|
52
|
+
"plugin_property",
|
41
53
|
"spec",
|
42
54
|
"start",
|
43
55
|
]
|
@@ -1,13 +1,13 @@
|
|
1
1
|
import functools
|
2
|
+
import inspect
|
2
3
|
from collections.abc import Callable, Iterable
|
3
4
|
from typing import Any, overload
|
4
5
|
|
5
6
|
import attrs
|
6
|
-
import wrapt
|
7
7
|
|
8
8
|
from liblaf import grapes
|
9
9
|
|
10
|
-
from .
|
10
|
+
from .typing import MethodName, PluginId
|
11
11
|
|
12
12
|
|
13
13
|
@attrs.define
|
@@ -43,18 +43,31 @@ def impl(
|
|
43
43
|
if func is None:
|
44
44
|
return functools.partial(impl, priority=priority, after=after, before=before)
|
45
45
|
|
46
|
-
|
46
|
+
info = ImplInfo(after=after, before=before, priority=priority)
|
47
|
+
|
48
|
+
@grapes.decorator
|
47
49
|
def wrapper(
|
48
50
|
wrapped: Callable, _instance: Any, args: tuple, kwargs: dict[str, Any]
|
49
51
|
) -> Any:
|
50
52
|
return wrapped(*args, **kwargs)
|
51
53
|
|
52
|
-
|
53
|
-
|
54
|
-
return
|
54
|
+
func = wrapper(func)
|
55
|
+
grapes.wrapt_setattr(func, "impl", info)
|
56
|
+
return func
|
57
|
+
|
58
|
+
|
59
|
+
def collect_impls(cls: Any) -> dict[MethodName, ImplInfo]:
|
60
|
+
if isinstance(cls, type):
|
61
|
+
cls = type(cls)
|
62
|
+
return {
|
63
|
+
name: grapes.wrapt_getattr(method, "impl")
|
64
|
+
for name, method in inspect.getmembers(
|
65
|
+
cls, lambda m: grapes.wrapt_getattr(m, "impl", None) is not None
|
66
|
+
)
|
67
|
+
}
|
55
68
|
|
56
69
|
|
57
70
|
def get_impl_info(func: Callable | None) -> ImplInfo | None:
|
58
71
|
if func is None:
|
59
72
|
return None
|
60
|
-
return grapes.
|
73
|
+
return grapes.wrapt_getattr(func, "impl", None)
|
@@ -0,0 +1,99 @@
|
|
1
|
+
import math
|
2
|
+
import operator
|
3
|
+
from collections.abc import Mapping, Sequence
|
4
|
+
from typing import Any, Self
|
5
|
+
|
6
|
+
import attrs
|
7
|
+
import cachetools
|
8
|
+
import networkx as nx
|
9
|
+
from loguru import logger
|
10
|
+
|
11
|
+
from ._impl import ImplInfo, collect_impls, get_impl_info
|
12
|
+
from .typing import MethodName, PluginId
|
13
|
+
|
14
|
+
|
15
|
+
@attrs.define
|
16
|
+
class Plugin:
|
17
|
+
plugins: dict[PluginId, "Plugin"] = attrs.field(factory=dict, kw_only=True)
|
18
|
+
|
19
|
+
_plugin_parent: Self | None = attrs.field(default=None, kw_only=True)
|
20
|
+
_cache_sort_plugins: cachetools.Cache[MethodName, Sequence["Plugin"]] = attrs.field(
|
21
|
+
factory=lambda: cachetools.Cache(math.inf), init=False
|
22
|
+
)
|
23
|
+
|
24
|
+
@property
|
25
|
+
def plugin_id(self) -> str:
|
26
|
+
return type(self).__name__
|
27
|
+
|
28
|
+
@property
|
29
|
+
def plugin_root(self) -> Self:
|
30
|
+
if self._plugin_parent is None:
|
31
|
+
return self
|
32
|
+
return self._plugin_parent.plugin_root
|
33
|
+
|
34
|
+
def delegate(
|
35
|
+
self,
|
36
|
+
method: MethodName,
|
37
|
+
args: Sequence[Any] = (),
|
38
|
+
kwargs: Mapping[str, Any] = {},
|
39
|
+
*,
|
40
|
+
first_result: bool = False,
|
41
|
+
) -> Any:
|
42
|
+
plugins: Sequence[Plugin] = self._plugins_sort(method)
|
43
|
+
if not plugins:
|
44
|
+
if first_result:
|
45
|
+
return None
|
46
|
+
return []
|
47
|
+
results: list[Any] = []
|
48
|
+
for plugin in plugins:
|
49
|
+
try:
|
50
|
+
result: Any = getattr(plugin, method)(*args, **kwargs)
|
51
|
+
except BaseException as e:
|
52
|
+
if isinstance(e, (KeyboardInterrupt, SystemExit)):
|
53
|
+
raise
|
54
|
+
logger.exception("Plugin {}", plugin.plugin_id)
|
55
|
+
else:
|
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
|
+
impls: dict[MethodName, ImplInfo] = collect_impls(plugin)
|
65
|
+
for name in impls:
|
66
|
+
self._cache_sort_plugins.pop(name, None)
|
67
|
+
plugin._plugin_parent = self # noqa: SLF001
|
68
|
+
self.plugins[plugin.plugin_id] = plugin
|
69
|
+
|
70
|
+
def _plugins_sort_cache_key(self, method: MethodName) -> MethodName:
|
71
|
+
return method
|
72
|
+
|
73
|
+
@cachetools.cachedmethod(
|
74
|
+
operator.attrgetter("_cache_sort_plugins"), key=_plugins_sort_cache_key
|
75
|
+
)
|
76
|
+
def _plugins_sort(self, method: str) -> Sequence["Plugin"]:
|
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
|
+
|
83
|
+
def key_fn(node: str) -> int:
|
84
|
+
return plugin_infos[node].priority
|
85
|
+
|
86
|
+
graph: nx.DiGraph[str] = nx.DiGraph()
|
87
|
+
for plugin_id, impl_info in plugin_infos.items():
|
88
|
+
graph.add_node(plugin_id)
|
89
|
+
for after in impl_info.after:
|
90
|
+
if after in plugin_infos:
|
91
|
+
graph.add_edge(after, plugin_id)
|
92
|
+
for before in impl_info.before:
|
93
|
+
if before in plugin_infos:
|
94
|
+
graph.add_edge(plugin_id, before)
|
95
|
+
return tuple(
|
96
|
+
plugin
|
97
|
+
for plugin_id in nx.lexicographical_topological_sort(graph, key=key_fn)
|
98
|
+
if (plugin := self.plugins.get(plugin_id)) is not None
|
99
|
+
)
|