mirror of
https://github.com/Stone-Red-Code/dvc.git
synced 2026-11-02 07:18:25 +01:00
exp: add structured datatypes for collection
This commit is contained in:
committed by
Peter Rowlands (변기호)
parent
10f523430b
commit
2e8bcbf719
@@ -1,7 +1,7 @@
|
|||||||
import json
|
import json
|
||||||
from dataclasses import asdict, dataclass, field
|
from dataclasses import asdict, dataclass, field
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from typing import TYPE_CHECKING, Any, Callable, Dict, Optional
|
from typing import TYPE_CHECKING, Any, Callable, Dict, Iterator, List, Literal, Optional
|
||||||
|
|
||||||
from dvc.exceptions import DvcException
|
from dvc.exceptions import DvcException
|
||||||
from dvc.repo.metrics.show import _gather_metrics
|
from dvc.repo.metrics.show import _gather_metrics
|
||||||
@@ -25,17 +25,15 @@ class _ISOEncoder(json.JSONEncoder):
|
|||||||
|
|
||||||
@dataclass(frozen=True)
|
@dataclass(frozen=True)
|
||||||
class SerializableExp:
|
class SerializableExp:
|
||||||
"""Serializable experiment state."""
|
"""Serializable experiment data."""
|
||||||
|
|
||||||
rev: str
|
rev: str
|
||||||
timestamp: Optional[datetime] = None
|
timestamp: Optional[datetime] = None
|
||||||
params: Dict[str, Any] = field(default_factory=dict)
|
params: Dict[str, Any] = field(default_factory=dict)
|
||||||
metrics: Dict[str, Any] = field(default_factory=dict)
|
metrics: Dict[str, Any] = field(default_factory=dict)
|
||||||
deps: Dict[str, "_ExpDep"] = field(default_factory=dict)
|
deps: Dict[str, "ExpDep"] = field(default_factory=dict)
|
||||||
outs: Dict[str, "_ExpOut"] = field(default_factory=dict)
|
outs: Dict[str, "ExpOut"] = field(default_factory=dict)
|
||||||
status: Optional[str] = None
|
meta: Dict[str, Any] = field(default_factory=dict)
|
||||||
executor: Optional[str] = None
|
|
||||||
error: Optional["SerializableError"] = None
|
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_repo(
|
def from_repo(
|
||||||
@@ -43,6 +41,7 @@ class SerializableExp:
|
|||||||
repo: "Repo",
|
repo: "Repo",
|
||||||
rev: Optional[str] = None,
|
rev: Optional[str] = None,
|
||||||
onerror: Optional[Callable] = None,
|
onerror: Optional[Callable] = None,
|
||||||
|
param_deps: bool = False,
|
||||||
**kwargs,
|
**kwargs,
|
||||||
) -> "SerializableExp":
|
) -> "SerializableExp":
|
||||||
"""Returns a SerializableExp from the current repo state.
|
"""Returns a SerializableExp from the current repo state.
|
||||||
@@ -57,29 +56,26 @@ class SerializableExp:
|
|||||||
|
|
||||||
rev = rev or repo.get_rev()
|
rev = rev or repo.get_rev()
|
||||||
assert rev
|
assert rev
|
||||||
status: Optional[str] = kwargs.get("status")
|
|
||||||
# NOTE: _gather_params/_gather_metrics return defaultdict which is not
|
# NOTE: _gather_params/_gather_metrics return defaultdict which is not
|
||||||
# supported in dataclasses.asdict() on all python releases
|
# supported in dataclasses.asdict() on all python releases
|
||||||
# see https://bugs.python.org/issue35540
|
# see https://bugs.python.org/issue35540
|
||||||
params = dict(_gather_params(repo, onerror=onerror))
|
params = dict(_gather_params(repo, deps=param_deps, onerror=onerror))
|
||||||
if status and status.lower() in ("queued", "failed"):
|
metrics = dict(
|
||||||
metrics: Dict[str, Any] = {}
|
_gather_metrics(
|
||||||
else:
|
repo,
|
||||||
metrics = dict(
|
targets=None,
|
||||||
_gather_metrics(
|
rev=rev[:7],
|
||||||
repo,
|
recursive=False,
|
||||||
targets=None,
|
onerror=onerror_collect,
|
||||||
rev=rev[:7],
|
|
||||||
recursive=False,
|
|
||||||
onerror=onerror_collect,
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
|
)
|
||||||
|
meta = cls._gather_meta(repo)
|
||||||
return cls(
|
return cls(
|
||||||
rev=rev,
|
rev=rev,
|
||||||
params=params,
|
params=params,
|
||||||
metrics=metrics,
|
metrics=metrics,
|
||||||
deps={
|
deps={
|
||||||
relpath(dep.fs_path, repo.root_dir): _ExpDep(
|
relpath(dep.fs_path, repo.root_dir): ExpDep(
|
||||||
hash=dep.hash_info.value if dep.hash_info else None,
|
hash=dep.hash_info.value if dep.hash_info else None,
|
||||||
size=dep.meta.size if dep.meta else None,
|
size=dep.meta.size if dep.meta else None,
|
||||||
nfiles=dep.meta.nfiles if dep.meta else None,
|
nfiles=dep.meta.nfiles if dep.meta else None,
|
||||||
@@ -88,7 +84,7 @@ class SerializableExp:
|
|||||||
if not isinstance(dep, (ParamsDependency, RepoDependency))
|
if not isinstance(dep, (ParamsDependency, RepoDependency))
|
||||||
},
|
},
|
||||||
outs={
|
outs={
|
||||||
relpath(out.fs_path, repo.root_dir): _ExpOut(
|
relpath(out.fs_path, repo.root_dir): ExpOut(
|
||||||
hash=out.hash_info.value if out.hash_info else None,
|
hash=out.hash_info.value if out.hash_info else None,
|
||||||
size=out.meta.size if out.meta else None,
|
size=out.meta.size if out.meta else None,
|
||||||
nfiles=out.meta.nfiles if out.meta else None,
|
nfiles=out.meta.nfiles if out.meta else None,
|
||||||
@@ -98,9 +94,16 @@ class SerializableExp:
|
|||||||
for out in repo.index.outs
|
for out in repo.index.outs
|
||||||
if not (out.is_metric or out.is_plot)
|
if not (out.is_metric or out.is_plot)
|
||||||
},
|
},
|
||||||
|
meta=meta,
|
||||||
**kwargs,
|
**kwargs,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _gather_meta(repo: "Repo") -> Dict[str, Any]:
|
||||||
|
return {
|
||||||
|
"has_checkpoints": any(stage.is_checkpoint for stage in repo.index.stages)
|
||||||
|
}
|
||||||
|
|
||||||
def dumpd(self) -> Dict[str, Any]:
|
def dumpd(self) -> Dict[str, Any]:
|
||||||
return asdict(self)
|
return asdict(self)
|
||||||
|
|
||||||
@@ -113,6 +116,10 @@ class SerializableExp:
|
|||||||
parsed = json.loads(data)
|
parsed = json.loads(data)
|
||||||
if "timestamp" in parsed:
|
if "timestamp" in parsed:
|
||||||
parsed["timestamp"] = datetime.fromisoformat(parsed["timestamp"])
|
parsed["timestamp"] = datetime.fromisoformat(parsed["timestamp"])
|
||||||
|
if "deps" in parsed:
|
||||||
|
parsed["deps"] = {k: ExpDep(**v) for k, v in parsed["deps"].items()}
|
||||||
|
if "outs" in parsed:
|
||||||
|
parsed["outs"] = {k: ExpOut(**v) for k, v in parsed["outs"].items()}
|
||||||
return cls(**parsed)
|
return cls(**parsed)
|
||||||
except (TypeError, json.JSONDecodeError) as exc:
|
except (TypeError, json.JSONDecodeError) as exc:
|
||||||
raise DeserializeError("failed to load SerializableExp") from exc
|
raise DeserializeError("failed to load SerializableExp") from exc
|
||||||
@@ -120,8 +127,7 @@ class SerializableExp:
|
|||||||
@property
|
@property
|
||||||
def contains_error(self) -> bool:
|
def contains_error(self) -> bool:
|
||||||
return (
|
return (
|
||||||
self.error is not None
|
self.params.get("error")
|
||||||
or self.params.get("error")
|
|
||||||
or any(value.get("error") for value in self.params.values())
|
or any(value.get("error") for value in self.params.values())
|
||||||
or self.metrics.get("error")
|
or self.metrics.get("error")
|
||||||
or any(value.get("error") for value in self.metrics.values())
|
or any(value.get("error") for value in self.metrics.values())
|
||||||
@@ -129,14 +135,14 @@ class SerializableExp:
|
|||||||
|
|
||||||
|
|
||||||
@dataclass(frozen=True)
|
@dataclass(frozen=True)
|
||||||
class _ExpDep:
|
class ExpDep:
|
||||||
hash: Optional[str] # noqa: A003
|
hash: Optional[str] # noqa: A003
|
||||||
size: Optional[int]
|
size: Optional[int]
|
||||||
nfiles: Optional[int]
|
nfiles: Optional[int]
|
||||||
|
|
||||||
|
|
||||||
@dataclass(frozen=True)
|
@dataclass(frozen=True)
|
||||||
class _ExpOut:
|
class ExpOut:
|
||||||
hash: Optional[str] # noqa: A003
|
hash: Optional[str] # noqa: A003
|
||||||
size: Optional[int]
|
size: Optional[int]
|
||||||
nfiles: Optional[int]
|
nfiles: Optional[int]
|
||||||
@@ -162,3 +168,52 @@ class SerializableError:
|
|||||||
return cls(**parsed)
|
return cls(**parsed)
|
||||||
except (TypeError, json.JSONDecodeError) as exc:
|
except (TypeError, json.JSONDecodeError) as exc:
|
||||||
raise DeserializeError("failed to load SerializableError") from exc
|
raise DeserializeError("failed to load SerializableError") from exc
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class ExpState:
|
||||||
|
"""Git/DVC experiment state."""
|
||||||
|
|
||||||
|
rev: str
|
||||||
|
name: Optional[str] = None
|
||||||
|
data: Optional[SerializableExp] = None
|
||||||
|
error: Optional[SerializableError] = None
|
||||||
|
experiments: Optional[List["ExpRange"]] = None
|
||||||
|
|
||||||
|
def dumpd(self) -> Dict[str, Any]:
|
||||||
|
return asdict(self)
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class ExpRange:
|
||||||
|
revs: List["ExpState"]
|
||||||
|
executor: Optional["ExpExecutor"] = None
|
||||||
|
name: Optional[str] = None
|
||||||
|
|
||||||
|
def __len__(self) -> int:
|
||||||
|
return len(self.revs)
|
||||||
|
|
||||||
|
def __iter__(self) -> Iterator["ExpState"]:
|
||||||
|
return iter(self.revs)
|
||||||
|
|
||||||
|
def __getitem__(self, index: int) -> "ExpState":
|
||||||
|
return self.revs[index]
|
||||||
|
|
||||||
|
def dumpd(self) -> Dict[str, Any]:
|
||||||
|
return asdict(self)
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class LocalExpExecutor:
|
||||||
|
root: Optional[str] = None
|
||||||
|
log: Optional[str] = None
|
||||||
|
pid: Optional[int] = None
|
||||||
|
returncode: Optional[int] = None
|
||||||
|
task_id: Optional[str] = None
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class ExpExecutor:
|
||||||
|
state: Literal["success", "queued", "running", "failed"]
|
||||||
|
name: Optional[str] = None
|
||||||
|
local: Optional[LocalExpExecutor] = None
|
||||||
|
|||||||
Reference in New Issue
Block a user