22
33from __future__ import annotations
44
5- from collections .abc import Mapping
5+ from collections .abc import Mapping , Sequence
66from dataclasses import dataclass , field
7- from typing import Any , Literal
7+ from typing import Any , Literal , cast
88
99from .status import Stage1ErrorRecord , Stage1SubstepStatus
1010
@@ -27,6 +27,24 @@ class DatasetCommandResult:
2727 error : Stage1ErrorRecord | None = None
2828 metadata : Mapping [str , Any ] = field (default_factory = dict )
2929
30+ @classmethod
31+ def from_dict (cls , data : Mapping [str , Any ]) -> "DatasetCommandResult" :
32+ """Build a command result from a JSON-compatible payload."""
33+
34+ error = _error_record_from_payload (data .get ("error" ))
35+ return cls (
36+ command_name = str (data ["command_name" ]),
37+ argv = _string_tuple (data .get ("argv" , ())),
38+ status = _command_execution_status (data ["status" ]),
39+ returncode = _optional_int (data .get ("returncode" )),
40+ started_at = str (data ["started_at" ]),
41+ completed_at = str (data ["completed_at" ]),
42+ duration_s = float (data ["duration_s" ]),
43+ combined_output_tail = _string_tuple (data .get ("combined_output_tail" , ())),
44+ error = error ,
45+ metadata = _metadata_mapping (data .get ("metadata" , {})),
46+ )
47+
3048 def to_dict (self ) -> dict [str , Any ]:
3149 """Return a JSON-compatible command result payload."""
3250
@@ -60,6 +78,32 @@ class DatasetSubstepResult:
6078 error : Stage1ErrorRecord | None = None
6179 metadata : Mapping [str , Any ] = field (default_factory = dict )
6280
81+ @classmethod
82+ def from_dict (cls , data : Mapping [str , Any ]) -> "DatasetSubstepResult" :
83+ """Build a substep result from a JSON-compatible payload."""
84+
85+ command_results = data .get ("command_results" , ())
86+ if not isinstance (command_results , Sequence ) or isinstance (
87+ command_results , str
88+ ):
89+ raise TypeError ("command_results must be a sequence" )
90+ return cls (
91+ substep_id = str (data ["substep_id" ]),
92+ title = str (data ["title" ]),
93+ status = _stage_1_substep_status (data ["status" ]),
94+ started_at = _optional_str (data .get ("started_at" )),
95+ completed_at = str (data ["completed_at" ]),
96+ duration_s = _optional_float (data .get ("duration_s" )),
97+ command_names = _string_tuple (data .get ("command_names" , ())),
98+ command_results = tuple (
99+ DatasetCommandResult .from_dict (_mapping_payload (result ))
100+ for result in command_results
101+ ),
102+ artifact_paths = _string_tuple (data .get ("artifact_paths" , ())),
103+ error = _error_record_from_payload (data .get ("error" )),
104+ metadata = _metadata_mapping (data .get ("metadata" , {})),
105+ )
106+
63107 def to_dict (self ) -> dict [str , Any ]:
64108 """Return a JSON-compatible substep result payload."""
65109
@@ -78,6 +122,54 @@ def to_dict(self) -> dict[str, Any]:
78122 }
79123
80124
125+ def _command_execution_status (value : Any ) -> CommandExecutionStatus :
126+ if value in ("completed" , "failed" ):
127+ return cast (CommandExecutionStatus , value )
128+ raise ValueError (f"Invalid command execution status: { value !r} " )
129+
130+
131+ def _stage_1_substep_status (value : Any ) -> Stage1SubstepStatus :
132+ if value in ("started" , "completed" , "skipped" , "failed" ):
133+ return cast (Stage1SubstepStatus , value )
134+ raise ValueError (f"Invalid Stage 1 substep status: { value !r} " )
135+
136+
137+ def _string_tuple (value : Any ) -> tuple [str , ...]:
138+ if not isinstance (value , Sequence ) or isinstance (value , str ):
139+ raise TypeError ("Expected a sequence" )
140+ return tuple (str (item ) for item in value )
141+
142+
143+ def _mapping_payload (value : Any ) -> Mapping [str , Any ]:
144+ if not isinstance (value , Mapping ):
145+ raise TypeError ("Expected a mapping" )
146+ return value
147+
148+
149+ def _metadata_mapping (value : Any ) -> Mapping [str , Any ]:
150+ if not isinstance (value , Mapping ):
151+ raise TypeError ("metadata must be a mapping" )
152+ return dict (value )
153+
154+
155+ def _error_record_from_payload (value : Any ) -> Stage1ErrorRecord | None :
156+ if value is None :
157+ return None
158+ return Stage1ErrorRecord .from_dict (_mapping_payload (value ))
159+
160+
161+ def _optional_str (value : Any ) -> str | None :
162+ return None if value is None else str (value )
163+
164+
165+ def _optional_int (value : Any ) -> int | None :
166+ return None if value is None else int (value )
167+
168+
169+ def _optional_float (value : Any ) -> float | None :
170+ return None if value is None else float (value )
171+
172+
81173__all__ = [
82174 "CommandExecutionStatus" ,
83175 "DatasetCommandResult" ,
0 commit comments