@@ -1802,6 +1802,69 @@ def pipeline_runs_list(self, **kwargs: Any) -> dict[str, Any]:
18021802 assert len (client .list_calls ) == 4
18031803
18041804
1805+
1806+ def test_pipeline_runs_recovered_retry_runs_after_retry_submit_hook (tmp_path : Path , monkeypatch ) -> None :
1807+ monkeypatch .setattr ("tangle_cli.pipeline_run_manager.time.sleep" , lambda _seconds : None )
1808+ pipeline_path = tmp_path / "pipeline.yaml"
1809+ events : list [tuple [str , int , str | None ]] = []
1810+
1811+ class DynamicTimeHooks (PipelineRunnerHooks ):
1812+ def __init__ (self ) -> None :
1813+ super ().__init__ ()
1814+ self .prepare_run_arguments_calls = 0
1815+
1816+ def read_pipeline_yaml (self , pipeline_path ):
1817+ return {
1818+ "name" : "template-source" ,
1819+ "inputs" : [{"name" : "exec_time" , "type" : "String" }],
1820+ "metadata" : {"annotations" : {"run-name-template" : "run-${arguments.exec_time}" }},
1821+ "implementation" : {"graph" : {"tasks" : {}}},
1822+ }
1823+
1824+ def prepare_run_arguments (self , pipeline_spec , run_args ):
1825+ del pipeline_spec
1826+ self .prepare_run_arguments_calls += 1
1827+ updated = dict (run_args or {})
1828+ updated ["exec_time" ] = f"time-{ self .prepare_run_arguments_calls } "
1829+ return updated
1830+
1831+ def before_retry (self , context , error , * , next_attempt ):
1832+ del error
1833+ events .append (("before_retry" , next_attempt , context .run_id ))
1834+
1835+ def after_retry_submit (self , context ):
1836+ events .append (("after_retry_submit" , context .attempt , context .run_id ))
1837+
1838+ class RecoverOnRetryClient (FakeClient ):
1839+ def pipeline_runs_create (self , body : Any = None ) -> dict [str , Any ]:
1840+ self .created .append (copy .deepcopy (body ))
1841+ raise TimeoutError ("submit timed out" )
1842+
1843+ def pipeline_runs_list (self , ** kwargs : Any ) -> dict [str , Any ]:
1844+ self .list_calls .append (kwargs )
1845+ if len (self .list_calls ) <= 2 :
1846+ return {"pipeline_runs" : [], "next_page_token" : None }
1847+ return {
1848+ "pipeline_runs" : [
1849+ {"id" : "run-created" , "root_execution_id" : "exec-created" , "pipeline_name" : "run-time-1" }
1850+ ],
1851+ "next_page_token" : None ,
1852+ }
1853+
1854+ hooks = DynamicTimeHooks ()
1855+ client = RecoverOnRetryClient ()
1856+ manager = PipelineRunner (client = client , hooks = hooks )
1857+
1858+ result = manager .run_pipeline (pipeline_path , hydrate = False , max_attempts = 2 )
1859+
1860+ assert result ["response" ]["id" ] == "run-created"
1861+ assert result ["context" ].attempt == 2
1862+ assert result ["context" ].metadata ["recovered_after_submit_error" ] is True
1863+ assert hooks .prepare_run_arguments_calls == 1
1864+ assert len (client .created ) == 1
1865+ assert events == [("before_retry" , 2 , None ), ("after_retry_submit" , 2 , "run-created" )]
1866+
1867+
18051868def test_pipeline_runs_run_pipeline_spec_uses_in_memory_spec_lifecycle () -> None :
18061869 events = []
18071870 spec = {
0 commit comments