liblaf-cherries 0.5.0__py3-none-any.whl → 0.5.2__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 (45) hide show
  1. liblaf/cherries/__init__.py +1 -0
  2. liblaf/cherries/__init__.pyi +3 -3
  3. liblaf/cherries/_entrypoint.py +9 -8
  4. liblaf/cherries/_version.py +2 -2
  5. liblaf/cherries/config/__init__.py +1 -0
  6. liblaf/cherries/config/__init__.pyi +16 -6
  7. liblaf/cherries/config/_config.py +1 -2
  8. liblaf/cherries/{pathutils → config/asset}/__init__.py +1 -0
  9. liblaf/cherries/config/asset/__init__.pyi +34 -0
  10. liblaf/cherries/config/asset/_meta.py +98 -0
  11. liblaf/cherries/config/asset/_registry.py +25 -0
  12. liblaf/cherries/config/asset/resolvers/__init__.py +4 -0
  13. liblaf/cherries/config/asset/resolvers/__init__.pyi +5 -0
  14. liblaf/cherries/config/asset/resolvers/_abc.py +17 -0
  15. liblaf/cherries/config/asset/resolvers/_series.py +18 -0
  16. liblaf/cherries/config/asset/resolvers/_vtk.py +33 -0
  17. liblaf/cherries/core/__init__.py +1 -0
  18. liblaf/cherries/core/__init__.pyi +15 -3
  19. liblaf/cherries/core/_impl.py +20 -7
  20. liblaf/cherries/core/_plugin.py +51 -48
  21. liblaf/cherries/core/_run.py +17 -36
  22. liblaf/cherries/core/_spec.py +10 -11
  23. liblaf/cherries/core/_utils.py +44 -1
  24. liblaf/cherries/meta/__init__.py +1 -0
  25. liblaf/cherries/meta/_git.py +3 -3
  26. liblaf/cherries/meta/_name.py +3 -3
  27. liblaf/cherries/path_utils/__init__.py +4 -0
  28. liblaf/cherries/plugins/__init__.py +1 -0
  29. liblaf/cherries/plugins/comet.py +4 -4
  30. liblaf/cherries/plugins/git_.py +12 -10
  31. liblaf/cherries/plugins/logging.py +2 -2
  32. liblaf/cherries/profiles/__init__.py +1 -0
  33. liblaf/cherries/profiles/_factory.py +2 -3
  34. liblaf/cherries/utils/__init__.py +1 -0
  35. {liblaf_cherries-0.5.0.dist-info → liblaf_cherries-0.5.2.dist-info}/METADATA +4 -3
  36. liblaf_cherries-0.5.2.dist-info/RECORD +56 -0
  37. liblaf/cherries/config/_asset.py +0 -115
  38. liblaf_cherries-0.5.0.dist-info/RECORD +0 -48
  39. /liblaf/cherries/core/{typed.py → typing.py} +0 -0
  40. /liblaf/cherries/{pathutils → path_utils}/__init__.pyi +0 -0
  41. /liblaf/cherries/{pathutils → path_utils}/_convert.py +0 -0
  42. /liblaf/cherries/{pathutils → path_utils}/_path.py +0 -0
  43. /liblaf/cherries/{pathutils → path_utils}/_special.py +0 -0
  44. {liblaf_cherries-0.5.0.dist-info → liblaf_cherries-0.5.2.dist-info}/WHEEL +0 -0
  45. {liblaf_cherries-0.5.0.dist-info → liblaf_cherries-0.5.2.dist-info}/licenses/LICENSE +0 -0
@@ -1,3 +1,4 @@
1
1
  import lazy_loader as lazy
2
2
 
3
3
  __getattr__, __dir__, __all__ = lazy.attach_stub(__name__, __file__)
4
+ del lazy
@@ -1,4 +1,4 @@
1
- from . import config, core, meta, pathutils, plugins
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,7 +15,7 @@ from .core import (
15
15
  log_parameter,
16
16
  log_parameters,
17
17
  )
18
- from .pathutils import (
18
+ from .path_utils import (
19
19
  as_os_path,
20
20
  as_path,
21
21
  as_posix,
@@ -56,7 +56,7 @@ __all__ = [
56
56
  "output",
57
57
  "params",
58
58
  "path",
59
- "pathutils",
59
+ "path_utils",
60
60
  "plugins",
61
61
  "project_dir",
62
62
  "run",
@@ -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
- result: T = main(*args, **kwargs)
31
- for config in configs:
32
- for path in _config.get_outputs(config):
33
- run.log_output(path)
34
- run.end()
35
- return result
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(
@@ -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.5.0'
32
- __version_tuple__ = version_tuple = (0, 5, 0)
31
+ __version__ = version = '0.5.2'
32
+ __version_tuple__ = version_tuple = (0, 5, 2)
33
33
 
34
34
  __commit_id__ = commit_id = None
@@ -1,3 +1,4 @@
1
1
  import lazy_loader as lazy
2
2
 
3
3
  __getattr__, __dir__, __all__ = lazy.attach_stub(__name__, __file__)
4
+ del lazy
@@ -1,27 +1,37 @@
1
- from ._asset import (
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
- PathGenerator,
10
+ asset,
11
+ asset_resolver_registry,
5
12
  get_assets,
6
13
  get_inputs,
7
14
  get_outputs,
8
15
  input, # noqa: A004
9
16
  model_dump_without_assets,
10
17
  output,
11
- path_generators,
12
18
  )
13
- from ._config import BaseConfig
14
19
 
15
20
  __all__ = [
16
21
  "AssetKind",
22
+ "AssetResolver",
23
+ "AssetResolverRegistry",
24
+ "AssetResolverSeries",
25
+ "AssetResolverVtk",
17
26
  "BaseConfig",
27
+ "Extra",
18
28
  "MetaAsset",
19
- "PathGenerator",
29
+ "asset",
30
+ "asset_resolver_registry",
20
31
  "get_assets",
21
32
  "get_inputs",
22
33
  "get_outputs",
23
34
  "input",
24
35
  "model_dump_without_assets",
25
36
  "output",
26
- "path_generators",
27
37
  ]
@@ -2,5 +2,4 @@ import pydantic_settings as ps
2
2
 
3
3
 
4
4
  class BaseConfig(ps.BaseSettings):
5
- model_config = ps.SettingsConfigDict(cli_parse_args=True)
6
- # TODO: add support for config files
5
+ model_config = ps.SettingsConfigDict()
@@ -1,3 +1,4 @@
1
1
  import lazy_loader as lazy
2
2
 
3
3
  __getattr__, __dir__, __all__ = lazy.attach_stub(__name__, __file__)
4
+ del lazy
@@ -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,4 @@
1
+ import lazy_loader as lazy
2
+
3
+ __getattr__, __dir__, __all__ = lazy.attach_stub(__name__, __file__)
4
+ del lazy
@@ -0,0 +1,5 @@
1
+ from ._abc import AssetResolver
2
+ from ._series import AssetResolverSeries
3
+ from ._vtk import AssetResolverVtk
4
+
5
+ __all__ = ["AssetResolver", "AssetResolverSeries", "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,3 +1,4 @@
1
1
  import lazy_loader as lazy
2
2
 
3
3
  __getattr__, __dir__, __all__ = lazy.attach_stub(__name__, __file__)
4
+ del lazy
@@ -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 .typed import MethodName, PluginId
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 .typed import PluginId
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
- @wrapt.decorator
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
- proxy: Any = wrapper(func) # pyright: ignore[reportCallIssue]
53
- proxy._self_impl = ImplInfo(after=after, before=before, priority=priority) # noqa: SLF001
54
- return proxy
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.unbind_getattr(func, "_self_impl", None)
73
+ return grapes.wrapt_getattr(func, "impl", None)
@@ -1,12 +1,15 @@
1
+ import math
2
+ import operator
1
3
  from collections.abc import Mapping, Sequence
2
4
  from typing import Any, Self
3
5
 
4
6
  import attrs
7
+ import cachetools
5
8
  import networkx as nx
9
+ from loguru import logger
6
10
 
7
- from ._impl import ImplInfo, get_impl_info
8
- from ._spec import SpecInfo, collect_specs
9
- from .typed import MethodName, PluginId
11
+ from ._impl import ImplInfo, collect_impls, get_impl_info
12
+ from .typing import MethodName, PluginId
10
13
 
11
14
 
12
15
  @attrs.define
@@ -14,17 +17,13 @@ class Plugin:
14
17
  plugins: dict[PluginId, "Plugin"] = attrs.field(factory=dict, kw_only=True)
15
18
 
16
19
  _plugin_parent: Self | None = attrs.field(default=None, kw_only=True)
17
- _sort_plugins_cache: dict[MethodName, Sequence["Plugin"]] = attrs.field(
18
- factory=dict, init=False
20
+ _cache_sort_plugins: cachetools.Cache[MethodName, Sequence["Plugin"]] = attrs.field(
21
+ factory=lambda: cachetools.Cache(math.inf), init=False
19
22
  )
20
23
 
21
- @classmethod
22
- def plugin_id_cls(cls) -> str:
23
- return cls.__name__
24
-
25
24
  @property
26
25
  def plugin_id(self) -> str:
27
- return self.plugin_id_cls()
26
+ return type(self).__name__
28
27
 
29
28
  @property
30
29
  def plugin_root(self) -> Self:
@@ -47,50 +46,54 @@ class Plugin:
47
46
  return []
48
47
  results: list[Any] = []
49
48
  for plugin in plugins:
50
- result: Any = getattr(plugin, method)(*args, **kwargs)
51
- if result is None:
52
- continue
53
- if first_result:
54
- return result
55
- results.append(result)
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)
56
61
  return results
57
62
 
58
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)
59
67
  plugin._plugin_parent = self # noqa: SLF001
60
68
  self.plugins[plugin.plugin_id] = plugin
61
69
 
62
- def _plugins_prepare(self) -> None:
63
- specs: dict[str, SpecInfo] = collect_specs(self)
64
- for method in specs:
65
- self._sort_plugins_cache[method] = self._plugins_sort(
66
- method, refresh_cache=True
67
- )
70
+ def _plugins_sort_cache_key(self, method: MethodName) -> MethodName:
71
+ return method
68
72
 
69
- def _plugins_sort(
70
- self, method: str, *, refresh_cache: bool = False
71
- ) -> Sequence["Plugin"]:
72
- if refresh_cache or method not in self._sort_plugins_cache:
73
- plugin_infos: dict[str, ImplInfo] = {
74
- plugin_id: info
75
- for plugin_id, plugin in self.plugins.items()
76
- if (info := get_impl_info(getattr(plugin, method, None))) is not None
77
- }
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
+ }
78
82
 
79
- def key_fn(node: str) -> int:
80
- return plugin_infos[node].priority
83
+ def key_fn(node: str) -> int:
84
+ return plugin_infos[node].priority
81
85
 
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
- if after in plugin_infos:
87
- graph.add_edge(after, plugin_id)
88
- for before in impl_info.before:
89
- if before in plugin_infos:
90
- graph.add_edge(plugin_id, before)
91
- self._sort_plugins_cache[method] = tuple(
92
- plugin
93
- for plugin_id in nx.lexicographical_topological_sort(graph, key=key_fn)
94
- if (plugin := self.plugins.get(plugin_id)) is not None
95
- )
96
- return self._sort_plugins_cache[method]
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
+ )
@@ -1,17 +1,17 @@
1
1
  import contextlib
2
2
  import datetime
3
- import functools
4
3
  from collections.abc import Mapping
5
4
  from pathlib import Path
6
5
  from typing import Any
7
6
 
8
7
  import attrs
9
8
 
10
- from liblaf.cherries import pathutils
9
+ from liblaf.cherries import path_utils
11
10
  from liblaf.cherries.typing import PathLike
12
11
 
13
12
  from ._plugin import Plugin
14
13
  from ._spec import spec
14
+ from ._utils import plugin_cached_property, plugin_property
15
15
 
16
16
 
17
17
  @attrs.define
@@ -24,59 +24,41 @@ class Run(Plugin):
24
24
  3. [MLflow Tracking APIs | MLflow](https://www.mlflow.org/docs/latest/ml/tracking/tracking-api/)
25
25
  """
26
26
 
27
- @functools.cached_property
27
+ @plugin_cached_property
28
28
  def data_dir(self) -> Path:
29
- if self._plugin_parent is not None:
30
- return self.plugin_root.data_dir
31
- return pathutils.data()
29
+ return path_utils.data()
32
30
 
33
- @functools.cached_property
31
+ @plugin_cached_property
34
32
  def entrypoint(self) -> Path:
35
- if self._plugin_parent is not None:
36
- return self.plugin_root.entrypoint
37
- return pathutils.entrypoint()
33
+ return path_utils.entrypoint()
38
34
 
39
- @functools.cached_property
35
+ @plugin_cached_property
40
36
  def exp_dir(self) -> Path:
41
- if self._plugin_parent is not None:
42
- return self.plugin_root.exp_dir
43
- return pathutils.exp_dir()
37
+ return path_utils.exp_dir()
44
38
 
45
- @functools.cached_property
39
+ @plugin_cached_property
46
40
  def name(self) -> str:
47
- if self._plugin_parent is not None:
48
- return self.plugin_root.name
49
41
  return self.start_time.strftime("%Y-%m-%dT%H%M%S")
50
42
 
51
- @property
43
+ @plugin_property
52
44
  def params(self) -> Mapping[str, Any]:
53
- if self._plugin_parent is not None:
54
- return self.plugin_root.params
55
- return self.get_params()
45
+ return self.plugin_root.get_params()
56
46
 
57
- @functools.cached_property
47
+ @plugin_cached_property
58
48
  def project_name(self) -> str | None:
59
- if self._plugin_parent is not None:
60
- return self.plugin_root.project_name
61
49
  return self.project_dir.name
62
50
 
63
- @functools.cached_property
51
+ @plugin_cached_property
64
52
  def project_dir(self) -> Path:
65
- if self._plugin_parent is not None:
66
- return self.plugin_root.project_dir
67
- return pathutils.project_dir()
53
+ return path_utils.project_dir()
68
54
 
69
- @functools.cached_property
55
+ @plugin_cached_property
70
56
  def start_time(self) -> datetime.datetime:
71
- if self._plugin_parent is not None:
72
- return self.plugin_root.start_time
73
57
  return datetime.datetime.now().astimezone()
74
58
 
75
- @functools.cached_property
59
+ @plugin_property
76
60
  def url(self) -> str:
77
- if self._plugin_parent is not None:
78
- return self.plugin_root.url
79
- return self.get_url()
61
+ return self.plugin_root.get_url()
80
62
 
81
63
  @spec
82
64
  def end(self, *args, **kwargs) -> None: ...
@@ -170,7 +152,6 @@ class Run(Plugin):
170
152
 
171
153
  @spec(delegate=False)
172
154
  def start(self, *args, **kwargs) -> None:
173
- self._plugins_prepare()
174
155
  self.delegate("start", args, kwargs)
175
156
 
176
157
 
@@ -4,11 +4,10 @@ from collections.abc import Callable, Mapping, Sequence
4
4
  from typing import Any, Protocol, overload
5
5
 
6
6
  import attrs
7
- import wrapt
8
7
 
9
8
  from liblaf import grapes
10
9
 
11
- from .typed import MethodName
10
+ from .typing import MethodName
12
11
 
13
12
 
14
13
  class Plugin(Protocol):
@@ -48,27 +47,27 @@ def spec(
48
47
 
49
48
  info = SpecInfo(delegate=delegate, first_result=first_result)
50
49
 
51
- @wrapt.decorator
52
- def wrapper(
53
- wrapped: Callable, instance: Plugin, args: tuple, kwargs: dict[str, Any]
54
- ) -> Any:
50
+ @grapes.decorator
51
+ def wrapper[**P, T](
52
+ wrapped: Callable[P, T], instance: Plugin, args: tuple, kwargs: dict[str, Any]
53
+ ) -> T:
55
54
  if info.delegate:
56
55
  return instance.delegate(
57
56
  wrapped.__name__, args, kwargs, first_result=info.first_result
58
57
  )
59
58
  return wrapped(*args, **kwargs)
60
59
 
61
- proxy: Any = wrapper(func) # pyright: ignore[reportCallIssue]
62
- proxy._self_spec = SpecInfo(delegate=delegate, first_result=first_result) # noqa: SLF001
63
- return proxy
60
+ func: Any = wrapper(func)
61
+ grapes.wrapt_setattr(func, "spec", info)
62
+ return func
64
63
 
65
64
 
66
65
  def collect_specs(cls: type[Plugin] | Plugin) -> dict[str, SpecInfo]:
67
66
  if isinstance(cls, type):
68
67
  cls = type(cls)
69
68
  return {
70
- name: grapes.unbind_getattr(method, "_self_spec")
69
+ name: grapes.wrapt_getattr(method, "spec")
71
70
  for name, method in inspect.getmembers(
72
- cls, lambda m: grapes.unbind_getattr(m, "_self_spec", None) is not None
71
+ cls, lambda m: grapes.wrapt_getattr(m, "spec", None) is not None
73
72
  )
74
73
  }
@@ -1,5 +1,6 @@
1
+ import functools
1
2
  from collections.abc import Callable
2
- from typing import TYPE_CHECKING, Any
3
+ from typing import TYPE_CHECKING, Any, Self, overload, override
3
4
 
4
5
  import wrapt
5
6
 
@@ -18,3 +19,45 @@ def delegate_property_to_root[C: Callable](func: C) -> C:
18
19
  return getattr(instance.plugin_root, wrapped.__name__)
19
20
 
20
21
  return func
22
+
23
+
24
+ class PluginCachedProperty[T](functools.cached_property[T]):
25
+ @overload
26
+ def __get__(self, instance: None, owner: type | None = None) -> Self: ...
27
+ @overload
28
+ def __get__(self, instance: object, owner: type | None = None) -> T: ...
29
+ @override
30
+ def __get__(self, instance: object | None, owner: type | None = None) -> Self | T:
31
+ if instance is None:
32
+ return super().__get__(instance, owner)
33
+ if (parent := getattr(instance, "_plugin_parent", None)) is not None:
34
+ instance = parent
35
+ return super().__get__(instance, owner)
36
+
37
+ @override
38
+ def __set__(self, instance: object, value: T) -> None:
39
+ assert self.attrname is not None
40
+ instance.__dict__[self.attrname] = value
41
+
42
+
43
+ class PluginProperty[T](property):
44
+ @overload
45
+ def __get__(self, instance: None, owner: type, /) -> Self: ...
46
+ @overload
47
+ def __get__(self, instance: Any, owner: type | None = None, /) -> Any: ...
48
+ def __get__(
49
+ self, instance: object | None, owner: type | None = None, /
50
+ ) -> Self | T:
51
+ if instance is None:
52
+ return super().__get__(instance, owner)
53
+ if (parent := getattr(instance, "_plugin_parent", None)) is not None:
54
+ instance = parent
55
+ return super().__get__(instance, owner)
56
+
57
+
58
+ def plugin_cached_property[T](func: Callable[[Any], T]) -> PluginCachedProperty[T]:
59
+ return PluginCachedProperty(func)
60
+
61
+
62
+ def plugin_property[T](fget: Callable[[Any], T]) -> PluginProperty[T]:
63
+ return PluginProperty(fget)
@@ -1,3 +1,4 @@
1
1
  import lazy_loader as lazy
2
2
 
3
3
  __getattr__, __dir__, __all__ = lazy.attach_stub(__name__, __file__)
4
+ del lazy
@@ -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
6
+ from liblaf.cherries import path_utils
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
- pathutils.exp_dir(absolute=True), search_parent_directories=True
43
+ path_utils.exp_dir(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(pathutils.exp_dir(absolute=True), search_parent_directories=True)
49
+ return git.Repo(path_utils.exp_dir(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 pathutils
6
+ from liblaf.cherries import path_utils
7
7
 
8
8
  from ._git import git_info
9
9
 
@@ -18,8 +18,8 @@ def project_name() -> str | None:
18
18
 
19
19
 
20
20
  def exp_name() -> str:
21
- exp_dir: Path = pathutils.exp_dir(absolute=False)
22
- exp_name: str = pathutils.as_posix(exp_dir)
21
+ exp_dir: Path = path_utils.exp_dir(absolute=False)
22
+ exp_name: str = path_utils.as_posix(exp_dir)
23
23
  exp_name = exp_name.removeprefix("exp")
24
24
  exp_name = exp_name.removeprefix("/")
25
25
  return exp_name
@@ -0,0 +1,4 @@
1
+ import lazy_loader as lazy
2
+
3
+ __getattr__, __dir__, __all__ = lazy.attach_stub(__name__, __file__)
4
+ del lazy
@@ -1,3 +1,4 @@
1
1
  import lazy_loader as lazy
2
2
 
3
3
  __getattr__, __dir__, __all__ = lazy.attach_stub(__name__, __file__)
4
+ del lazy
@@ -9,7 +9,7 @@ import dvc.api
9
9
  import dvc.exceptions
10
10
 
11
11
  from liblaf import grapes
12
- from liblaf.cherries import core, pathutils
12
+ from liblaf.cherries import core, path_utils
13
13
  from liblaf.cherries.typing import PathLike
14
14
 
15
15
 
@@ -49,7 +49,7 @@ class Comet(core.Run):
49
49
  **kwargs,
50
50
  ) -> None:
51
51
  path = Path(path)
52
- name = pathutils.as_posix(name)
52
+ name = path_utils.as_posix(name)
53
53
  try:
54
54
  # ? I don't know why, but `dvc.api.get_url` only works with this. Maybe a DVC bug?
55
55
  dvc_path: Path = path.absolute().relative_to(Path.cwd())
@@ -128,8 +128,8 @@ class Comet(core.Run):
128
128
  @core.impl(after=("Logging",))
129
129
  def start(self, *args, **kwargs) -> None:
130
130
  self.exp = comet_ml.start(
131
- project_name=self.plugin_root.project_name,
131
+ project_name=self.project_name,
132
132
  experiment_config=comet_ml.ExperimentConfig(
133
- disabled=self.disabled, name=self.plugin_root.name
133
+ disabled=self.disabled, name=self.name
134
134
  ),
135
135
  )
@@ -52,17 +52,19 @@ class Git(core.Run):
52
52
  self.repo = git.Repo(self.project_dir, search_parent_directories=True)
53
53
 
54
54
  def _make_commit_message(self) -> str:
55
- name: str = self.plugin_root.name
55
+ name: str = self.name
56
56
  message: str = f"chore(cherries): {name}\n\n"
57
- metadata: dict[str, Any] = {}
58
- metadata["cmd"] = shlex.join(sys.orig_argv)
59
- if url := self.plugin_root.url:
60
- metadata["url"] = url
61
- if params := self.plugin_root.get_params():
62
- metadata["params"] = params
57
+ meta: dict[str, Any] = {}
58
+ if url := self.url:
59
+ meta["url"] = url
60
+ meta["exp_dir"] = self.exp_dir.relative_to(self.project_dir)
61
+ meta["cwd"] = Path.cwd().relative_to(self.project_dir)
62
+ meta["cmd"] = shlex.join(sys.orig_argv)
63
+ if params := self.params:
64
+ meta["params"] = params
63
65
  if inputs := self.inputs:
64
- metadata["inputs"] = inputs
66
+ meta["inputs"] = inputs
65
67
  if outputs := self.outputs:
66
- metadata["outputs"] = outputs
67
- message += grapes.yaml.encode(metadata).decode()
68
+ meta["outputs"] = outputs
69
+ message += grapes.yaml.encode(meta).decode()
68
70
  return message
@@ -11,7 +11,7 @@ from liblaf.cherries import core
11
11
  class Logging(core.Run):
12
12
  @property
13
13
  def log_file(self) -> Path:
14
- return self.plugin_root.exp_dir / "run.log"
14
+ return self.exp_dir / "run.log"
15
15
 
16
16
  @override
17
17
  @core.impl
@@ -21,4 +21,4 @@ class Logging(core.Run):
21
21
  @override
22
22
  @core.impl
23
23
  def end(self, *args, **kwargs) -> None:
24
- self.plugin_root.log_asset(self.log_file)
24
+ self.log_asset(self.log_file)
@@ -1,3 +1,4 @@
1
1
  import lazy_loader as lazy
2
2
 
3
3
  __getattr__, __dir__, __all__ = lazy.attach_stub(__name__, __file__)
4
+ del lazy
@@ -8,16 +8,15 @@ from ._abc import Profile
8
8
  from ._default import ProfileDefault # noqa: F401
9
9
  from ._playground import ProfilePlayground # noqa: F401
10
10
 
11
- # for code-completion
12
11
  type ProfileName = Literal["default", "playground"] | str # noqa: PYI051
13
12
  type ProfileLike = ProfileName | Profile | type[Profile]
14
13
 
15
14
 
16
15
  def factory(profile: ProfileLike | None = None) -> Profile:
17
- if profile is None and env.bool("DEBUG", default=False):
16
+ if profile is None and env.bool("DEBUG", False):
18
17
  profile = "playground"
19
18
  if profile is None:
20
- profile = env.str("PROFILE", default="default")
19
+ profile = env.str("PROFILE", "default")
21
20
  if isinstance(profile, str):
22
21
  return Profile[profile]()
23
22
  if isinstance(profile, Profile):
@@ -1,3 +1,4 @@
1
1
  import lazy_loader as lazy
2
2
 
3
3
  __getattr__, __dir__, __all__ = lazy.attach_stub(__name__, __file__)
4
+ del lazy
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: liblaf-cherries
3
- Version: 0.5.0
3
+ Version: 0.5.2
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/
@@ -32,12 +32,13 @@ Classifier: Topic :: Utilities
32
32
  Classifier: Typing :: Typed
33
33
  Requires-Python: >=3.12
34
34
  Requires-Dist: attrs<26,>=25
35
+ Requires-Dist: cachetools<7,>=6
35
36
  Requires-Dist: comet-ml<4,>=3
36
- Requires-Dist: dvc[all]<4,>=3
37
+ Requires-Dist: dvc[ssh]<4,>=3
37
38
  Requires-Dist: environs<15,>=14
38
39
  Requires-Dist: gitpython<4,>=3
39
40
  Requires-Dist: lazy-loader<0.5,>=0.4
40
- Requires-Dist: liblaf-grapes<5,>=4
41
+ Requires-Dist: liblaf-grapes<6,>=5
41
42
  Requires-Dist: loguru<0.8,>=0.7
42
43
  Requires-Dist: networkx<4,>=3
43
44
  Requires-Dist: pydantic-settings<3,>=2
@@ -0,0 +1,56 @@
1
+ liblaf/cherries/__init__.py,sha256=MfsYnXmbxgr6l1QHFkUgtppkaBNzkeRle6dMNcWGdxk,106
2
+ liblaf/cherries/__init__.pyi,sha256=A7MFyMZodmbEPTCe44P1M8JoDYyFHJLjjWqwpoq8zGg,1047
3
+ liblaf/cherries/_entrypoint.py,sha256=mUTxSaIztxgZbnqRjPJdZOepurdb8es6M3cb6T3vQns,2317
4
+ liblaf/cherries/_version.py,sha256=LGYtjQ6cyPZC_N0AovMIeSYYDK21050nm3HYgDanQBM,704
5
+ liblaf/cherries/_version.pyi,sha256=Pnv4Bxw13LHeuVkPLPsTtnp4N4jOGcAfFJw05uMMgBY,108
6
+ liblaf/cherries/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
7
+ liblaf/cherries/typing.py,sha256=mim8QVtwczTSHyw5mhEdfFcXis9o32n0CZyu8BrEorE,50
8
+ liblaf/cherries/config/__init__.py,sha256=MfsYnXmbxgr6l1QHFkUgtppkaBNzkeRle6dMNcWGdxk,106
9
+ liblaf/cherries/config/__init__.pyi,sha256=T7qc9AMlDlI9tTLx2V-3FTeYV_CFGZoS74H9Zm1WoeY,686
10
+ liblaf/cherries/config/_config.py,sha256=xqm-jDyo0aEQoiwx6P4TQiB1xGuwy01k8t8QjalzREw,111
11
+ liblaf/cherries/config/asset/__init__.py,sha256=MfsYnXmbxgr6l1QHFkUgtppkaBNzkeRle6dMNcWGdxk,106
12
+ liblaf/cherries/config/asset/__init__.pyi,sha256=B1aEmUTqzk4ItYd4oiQKJhJsJkEhY-ODeNNaeYSq-44,701
13
+ liblaf/cherries/config/asset/_meta.py,sha256=Kc2IALf30PO5UOB3QGGS48ecJ51hsCtEV2sAiEiGi70,3076
14
+ liblaf/cherries/config/asset/_registry.py,sha256=0oz_mFbKkVamWcgRUbrxLD5Y_KRzR7y91S76tIt3dcg,772
15
+ liblaf/cherries/config/asset/resolvers/__init__.py,sha256=MfsYnXmbxgr6l1QHFkUgtppkaBNzkeRle6dMNcWGdxk,106
16
+ liblaf/cherries/config/asset/resolvers/__init__.pyi,sha256=eqvh9oPwnQenPs5KvzoZWrTEXwf31fd0lGlFJkIM2FU,180
17
+ liblaf/cherries/config/asset/resolvers/_abc.py,sha256=Z2JVGV1p4BJbftGJ3vVfGnHdlAWThWcMBimYdXT0f08,382
18
+ liblaf/cherries/config/asset/resolvers/_series.py,sha256=KBJsgmbsck_8uyPNpA8ZhkK0K1bCWOPm3SAV4IqStkQ,492
19
+ liblaf/cherries/config/asset/resolvers/_vtk.py,sha256=nOrUO9YwkmbV58zPf9xWw75sF9dWzH9YHcrn6mPrgso,722
20
+ liblaf/cherries/core/__init__.py,sha256=MfsYnXmbxgr6l1QHFkUgtppkaBNzkeRle6dMNcWGdxk,106
21
+ liblaf/cherries/core/__init__.pyi,sha256=L6acWsE350FQUx14RF94tttvrcvs5EpdA3WHVGmsOSo,1022
22
+ liblaf/cherries/core/_impl.py,sha256=YdUR7Kf68z3JERHB3_2bYSMEe1OCqEwZL3bPP802jjs,1763
23
+ liblaf/cherries/core/_plugin.py,sha256=hPdsKK_XwZSTNtIJwZIAlFgoelY7aqLXlBWsc5y6wIY,3297
24
+ liblaf/cherries/core/_run.py,sha256=BzbGxNFsVhv5j2G8FcAXCwrQKl97wfVC9Xfo89R605M,4255
25
+ liblaf/cherries/core/_spec.py,sha256=YyKQh6CJf3Yn2SuTnQXwCLN8OflnTh9t2FYP-tn5yEE,1850
26
+ liblaf/cherries/core/_utils.py,sha256=ElNiHkrxhntmtF_6kgoCnIvSx1nJBt71XLfRTrYC9a4,2144
27
+ liblaf/cherries/core/typing.py,sha256=razpiUtLAGFD9J4H5RbIEHKEXWzxFHFjtOBBRllhea4,42
28
+ liblaf/cherries/meta/__init__.py,sha256=MfsYnXmbxgr6l1QHFkUgtppkaBNzkeRle6dMNcWGdxk,106
29
+ liblaf/cherries/meta/__init__.pyi,sha256=kQFneP3IiV9rBIzpep_uX0z-5IRPrXkPmyNRt19j8fg,282
30
+ liblaf/cherries/meta/_git.py,sha256=4EM8txj7HZ93VP-lkedxh6f7DxiZmjea65b9Y3U0AZM,1228
31
+ liblaf/cherries/meta/_name.py,sha256=-uzzeXkjnKExm_xqsfOQPctwb6cZg7i-tR4fdXMYKjs,562
32
+ liblaf/cherries/path_utils/__init__.py,sha256=MfsYnXmbxgr6l1QHFkUgtppkaBNzkeRle6dMNcWGdxk,106
33
+ liblaf/cherries/path_utils/__init__.pyi,sha256=fxSfaa4GaYvlVbeXvdyavbrryeY7QjgPV6q1hLrQaA8,337
34
+ liblaf/cherries/path_utils/_convert.py,sha256=aIrFYE3UoZ9m85-0uYaS_eXmBqZbeJbTgezstbUQrFM,808
35
+ liblaf/cherries/path_utils/_path.py,sha256=PikGdRMF3b3ajrHCMIakXPaIwixC8ObMjXGa8n_ucvM,1102
36
+ liblaf/cherries/path_utils/_special.py,sha256=HVmH6lCnj1TVzjAEmO93MGMTQi7JQWss4sHSNMieczY,1100
37
+ liblaf/cherries/plugins/__init__.py,sha256=MfsYnXmbxgr6l1QHFkUgtppkaBNzkeRle6dMNcWGdxk,106
38
+ liblaf/cherries/plugins/__init__.pyi,sha256=dyTB5ZS78Kg_7oWeChk_h7Ry_gU9k1sDiL5YOmnoG7I,177
39
+ liblaf/cherries/plugins/comet.py,sha256=9nFk4QS7TFmp_8FNgUv0dwQD3DYmGEdDnhnUI3_LWZE,3994
40
+ liblaf/cherries/plugins/dvc.py,sha256=1ktBxHIPhAYoN_Bto1c5BOn9VxKVBDK85s5rmlB2vvE,1053
41
+ liblaf/cherries/plugins/git_.py,sha256=E8lPFEZjmiHy5CBADBvRa9eBHFz6BbvwL82Y_rOcP-w,2113
42
+ liblaf/cherries/plugins/local.py,sha256=m01v9ifvoAza8z_LiIWlZNNK2b-69wcWS29N54X1Vio,1679
43
+ liblaf/cherries/plugins/logging.py,sha256=lhs6Xm2WAh8t13mqjBs1su4gJMANFrKIm4vCqUz39xw,509
44
+ liblaf/cherries/profiles/__init__.py,sha256=MfsYnXmbxgr6l1QHFkUgtppkaBNzkeRle6dMNcWGdxk,106
45
+ liblaf/cherries/profiles/__init__.pyi,sha256=qXxy2LOG9hE0LKCnECdJSv2VoHhOTMVDE3sUKIuZKmw,292
46
+ liblaf/cherries/profiles/_abc.py,sha256=1tpRrocBZNHonWaj3a264GnL5UoGS_HqU06aNZpruqY,193
47
+ liblaf/cherries/profiles/_default.py,sha256=cfV003HBA5aGAZkgDDaHAyongFcxCWYsEFpSQNICcMs,440
48
+ liblaf/cherries/profiles/_factory.py,sha256=OONaNS5cuHpPL-GwpkQp4jio9ce4fgAGr7pDB9JvOs0,720
49
+ liblaf/cherries/profiles/_playground.py,sha256=Aru-7RVoxNhomPLUxDLiM5wD5ZCPLy5Eymw0xQL2F9g,384
50
+ liblaf/cherries/utils/__init__.py,sha256=MfsYnXmbxgr6l1QHFkUgtppkaBNzkeRle6dMNcWGdxk,106
51
+ liblaf/cherries/utils/__init__.pyi,sha256=F5aTcXpWVmUoctPbLfmQXKyuXYRspAIjaIzfL1_3Lrw,51
52
+ liblaf/cherries/utils/_functools.py,sha256=0Puwvj1Wq4kp3S--hI-CXwUBZ56AtfkqIzFHllQtuug,181
53
+ liblaf_cherries-0.5.2.dist-info/METADATA,sha256=2owxtYgSPs0Q85PG2aKeuuy1L22h9JthZRoW-FcnCLU,7055
54
+ liblaf_cherries-0.5.2.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
55
+ liblaf_cherries-0.5.2.dist-info/licenses/LICENSE,sha256=Ph4NzyU3lGVDeYv-mf8aRmImH8v9rVL9F362FV4G6Ow,1063
56
+ liblaf_cherries-0.5.2.dist-info/RECORD,,
@@ -1,115 +0,0 @@
1
- import enum
2
- from collections.abc import Callable, Iterable
3
- from pathlib import Path
4
- from typing import Any, Literal
5
-
6
- import pydantic
7
-
8
- from liblaf import grapes
9
- from liblaf.cherries import pathutils
10
- from liblaf.cherries.typing import PathLike
11
-
12
-
13
- class AssetKind(enum.StrEnum):
14
- INPUT = enum.auto()
15
- OUTPUT = enum.auto()
16
-
17
-
18
- type PathGenerator = (
19
- PathLike | Iterable[PathLike] | Callable[[PathLike], PathLike | Iterable[PathLike]]
20
- )
21
-
22
-
23
- path_generators: dict[str, PathGenerator] = {}
24
-
25
-
26
- def _path_generator_series(path: PathLike) -> list[Path]:
27
- path = Path(path)
28
- if (folder := path.with_suffix(".d")).exists():
29
- return [folder]
30
- if (folder := path.with_suffix("")).exists():
31
- return [folder]
32
- return []
33
-
34
-
35
- path_generators[".series"] = _path_generator_series
36
-
37
-
38
- class MetaAsset:
39
- kind: AssetKind
40
- _extra: PathGenerator | None = None
41
-
42
- def __init__(self, kind: AssetKind, extra: PathGenerator | None = None) -> None:
43
- self.kind = kind
44
- self._extra = extra
45
-
46
- def get_extra(self, value: Path) -> list[Path]:
47
- extra: PathGenerator | None = self._extra
48
- if extra is None:
49
- extra = path_generators.get(value.suffix)
50
- if extra is None:
51
- return []
52
- if callable(extra):
53
- extra = extra(value)
54
- extra = grapes.as_iterable(extra)
55
- return [Path(p) for p in extra]
56
-
57
-
58
- def asset(
59
- path: PathLike, extra: PathGenerator | None = None, *, kind: AssetKind, **kwargs
60
- ) -> Path:
61
- field_info: pydantic.fields.FieldInfo = pydantic.Field(
62
- pathutils.data(path), **kwargs
63
- ) # pyright: ignore[reportAssignmentType]
64
- field_info.metadata.append(MetaAsset(kind=kind, extra=extra))
65
- return field_info # pyright: ignore[reportReturnType]
66
-
67
-
68
- def get_assets(cfg: pydantic.BaseModel, kind: AssetKind) -> list[Path]:
69
- assets: list[Path] = []
70
- for name, info in type(cfg).model_fields.items():
71
- value: Any = getattr(cfg, name)
72
- if isinstance(value, pydantic.BaseModel):
73
- assets.extend(get_assets(value, kind))
74
- for meta in info.metadata:
75
- if isinstance(meta, MetaAsset) and meta.kind == kind:
76
- value: Path = Path(value)
77
- assets.append(value)
78
- assets.extend(meta.get_extra(value))
79
- return assets
80
-
81
-
82
- def get_inputs(cfg: pydantic.BaseModel) -> list[Path]:
83
- return get_assets(cfg, AssetKind.INPUT)
84
-
85
-
86
- def get_outputs(cfg: pydantic.BaseModel) -> list[Path]:
87
- return get_assets(cfg, AssetKind.OUTPUT)
88
-
89
-
90
- def input(path: PathLike, extra: PathGenerator | None = None, **kwargs) -> Path: # noqa: A001
91
- return asset(path, extra=extra, kind=AssetKind.INPUT, **kwargs)
92
-
93
-
94
- def model_dump_without_assets(
95
- model: pydantic.BaseModel,
96
- *,
97
- mode: str | Literal["json", "python"] = "json", # noqa: PYI051
98
- **kwargs,
99
- ) -> dict[str, Any]:
100
- data: dict[str, Any] = model.model_dump(mode=mode, **kwargs)
101
- for name, info in type(model).model_fields.items():
102
- value: Any = getattr(model, name)
103
- if isinstance(value, pydantic.BaseModel):
104
- value = model_dump_without_assets(value)
105
- for meta in info.metadata:
106
- if isinstance(meta, MetaAsset):
107
- del data[name]
108
- break
109
- else:
110
- data[name] = value
111
- return data
112
-
113
-
114
- def output(path: PathLike, extra: PathGenerator | None = None, **kwargs) -> Path:
115
- return asset(path, extra=extra, kind=AssetKind.OUTPUT, **kwargs)
@@ -1,48 +0,0 @@
1
- liblaf/cherries/__init__.py,sha256=OHb6Xou2v6u42swTgjRfzej4CIlRg4OmgOIQXUiRjKA,97
2
- liblaf/cherries/__init__.pyi,sha256=lbens0QYeWVUsnCHZ6fyAh51dXuUPX5nU_f-VUK16jo,1044
3
- liblaf/cherries/_entrypoint.py,sha256=6tUIM1SX__UcMonzPzqXMcbMhD1rif7Oc7FFtMKIM0Q,2268
4
- liblaf/cherries/_version.py,sha256=fvHpBU3KZKRinkriKdtAt3crenOyysELF-M9y3ozg3U,704
5
- liblaf/cherries/_version.pyi,sha256=Pnv4Bxw13LHeuVkPLPsTtnp4N4jOGcAfFJw05uMMgBY,108
6
- liblaf/cherries/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
7
- liblaf/cherries/typing.py,sha256=mim8QVtwczTSHyw5mhEdfFcXis9o32n0CZyu8BrEorE,50
8
- liblaf/cherries/config/__init__.py,sha256=OHb6Xou2v6u42swTgjRfzej4CIlRg4OmgOIQXUiRjKA,97
9
- liblaf/cherries/config/__init__.pyi,sha256=qD-iWfQVHg1vDcCpHg28LvHHxagMsS2bQT6CZE7mrH0,469
10
- liblaf/cherries/config/_asset.py,sha256=alEOKiXlYC6wnLZOARSmYMGjFFvGf0sXJ1_hBSK0cts,3463
11
- liblaf/cherries/config/_config.py,sha256=WPwwk-3O96FyHGb2W8__LDfHpXBHLQM44aOcrMPjDL4,171
12
- liblaf/cherries/core/__init__.py,sha256=OHb6Xou2v6u42swTgjRfzej4CIlRg4OmgOIQXUiRjKA,97
13
- liblaf/cherries/core/__init__.pyi,sha256=jgmGgzlTDlyFKHnSjNw3g22ZBuBQ07NmeRiq4qKZFdg,727
14
- liblaf/cherries/core/_impl.py,sha256=LCd4f5oX5bEMa4uX37r0yiPntqZM1Lwqrd7vKL1oc7w,1459
15
- liblaf/cherries/core/_plugin.py,sha256=wv_G-cZK1WHPX5JSLPvKrnFJRnEwRxPuS5szN7DTRTc,3187
16
- liblaf/cherries/core/_run.py,sha256=kNU04SxQSpZzWvx_tCUlfR-7ll3-LuQqFJD2gfcWyhQ,5039
17
- liblaf/cherries/core/_spec.py,sha256=F73wxygkc_3v0hOKwfFSGaLRHEDhhENWLQZakruuRq8,1950
18
- liblaf/cherries/core/_utils.py,sha256=WpS79yqTH0Kl9ewzCIAiUcxamHKzJOB7ZWhjq_13bzY,572
19
- liblaf/cherries/core/typed.py,sha256=razpiUtLAGFD9J4H5RbIEHKEXWzxFHFjtOBBRllhea4,42
20
- liblaf/cherries/meta/__init__.py,sha256=OHb6Xou2v6u42swTgjRfzej4CIlRg4OmgOIQXUiRjKA,97
21
- liblaf/cherries/meta/__init__.pyi,sha256=kQFneP3IiV9rBIzpep_uX0z-5IRPrXkPmyNRt19j8fg,282
22
- liblaf/cherries/meta/_git.py,sha256=89isF_ur7qvI4YSmiBTim8ds7DtCtHUfeiCkoK0g6G8,1225
23
- liblaf/cherries/meta/_name.py,sha256=igjQ8TLxQbTesvWKakQDpytAheNhZ-R1d1MRZhgMPxM,559
24
- liblaf/cherries/pathutils/__init__.py,sha256=OHb6Xou2v6u42swTgjRfzej4CIlRg4OmgOIQXUiRjKA,97
25
- liblaf/cherries/pathutils/__init__.pyi,sha256=fxSfaa4GaYvlVbeXvdyavbrryeY7QjgPV6q1hLrQaA8,337
26
- liblaf/cherries/pathutils/_convert.py,sha256=aIrFYE3UoZ9m85-0uYaS_eXmBqZbeJbTgezstbUQrFM,808
27
- liblaf/cherries/pathutils/_path.py,sha256=PikGdRMF3b3ajrHCMIakXPaIwixC8ObMjXGa8n_ucvM,1102
28
- liblaf/cherries/pathutils/_special.py,sha256=HVmH6lCnj1TVzjAEmO93MGMTQi7JQWss4sHSNMieczY,1100
29
- liblaf/cherries/plugins/__init__.py,sha256=OHb6Xou2v6u42swTgjRfzej4CIlRg4OmgOIQXUiRjKA,97
30
- liblaf/cherries/plugins/__init__.pyi,sha256=dyTB5ZS78Kg_7oWeChk_h7Ry_gU9k1sDiL5YOmnoG7I,177
31
- liblaf/cherries/plugins/comet.py,sha256=LquXaN4hTUhbSwKVeWtErWUeys5aoRK8nP38yETmJfU,4016
32
- liblaf/cherries/plugins/dvc.py,sha256=1ktBxHIPhAYoN_Bto1c5BOn9VxKVBDK85s5rmlB2vvE,1053
33
- liblaf/cherries/plugins/git_.py,sha256=UBXwEbU8FJ59aAZf99JPE6d56ULMXdkzRpUCyZKM7cM,2051
34
- liblaf/cherries/plugins/local.py,sha256=m01v9ifvoAza8z_LiIWlZNNK2b-69wcWS29N54X1Vio,1679
35
- liblaf/cherries/plugins/logging.py,sha256=A2-fd7H96Jtg1eOOmMYp7AmT3133vDHd4HAklhjH6n0,533
36
- liblaf/cherries/profiles/__init__.py,sha256=OHb6Xou2v6u42swTgjRfzej4CIlRg4OmgOIQXUiRjKA,97
37
- liblaf/cherries/profiles/__init__.pyi,sha256=qXxy2LOG9hE0LKCnECdJSv2VoHhOTMVDE3sUKIuZKmw,292
38
- liblaf/cherries/profiles/_abc.py,sha256=1tpRrocBZNHonWaj3a264GnL5UoGS_HqU06aNZpruqY,193
39
- liblaf/cherries/profiles/_default.py,sha256=cfV003HBA5aGAZkgDDaHAyongFcxCWYsEFpSQNICcMs,440
40
- liblaf/cherries/profiles/_factory.py,sha256=d-PaE8JYllkzcqQDiyi5aP52lFJOkFdLai3XXgSKAE8,758
41
- liblaf/cherries/profiles/_playground.py,sha256=Aru-7RVoxNhomPLUxDLiM5wD5ZCPLy5Eymw0xQL2F9g,384
42
- liblaf/cherries/utils/__init__.py,sha256=OHb6Xou2v6u42swTgjRfzej4CIlRg4OmgOIQXUiRjKA,97
43
- liblaf/cherries/utils/__init__.pyi,sha256=F5aTcXpWVmUoctPbLfmQXKyuXYRspAIjaIzfL1_3Lrw,51
44
- liblaf/cherries/utils/_functools.py,sha256=0Puwvj1Wq4kp3S--hI-CXwUBZ56AtfkqIzFHllQtuug,181
45
- liblaf_cherries-0.5.0.dist-info/METADATA,sha256=rgnXriF6xt__GYW-nOb9K1ywv1irJJI7EWh9KFHa_gA,7023
46
- liblaf_cherries-0.5.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
47
- liblaf_cherries-0.5.0.dist-info/licenses/LICENSE,sha256=Ph4NzyU3lGVDeYv-mf8aRmImH8v9rVL9F362FV4G6Ow,1063
48
- liblaf_cherries-0.5.0.dist-info/RECORD,,
File without changes
File without changes
File without changes
File without changes
File without changes