22
33import sys
44import json
5+ import time
56import asyncio
67from typing import Any , TypeVar , Callable , Awaitable
78from dataclasses import asdict
1314 RecipeContext ,
1415 ExampleCleanupStatus ,
1516 ExampleCleanupFailure ,
16- empty_cleanup_status ,
1717)
1818
1919T = TypeVar ("T" )
2020
2121
22+ def unique_name (prefix : str ) -> str :
23+ """Generate a unique name with timestamp and random suffix."""
24+ return f"{ prefix } -{ int (time .time ())} -{ hex (int (time .time () * 1000 ) % 0xFFFFFF )[2 :]} "
25+
26+
2227class _CleanupTracker :
2328 """Tracks cleanup actions and executes them in LIFO order."""
2429
@@ -56,6 +61,35 @@ def _should_fail_process(result: ExampleResult) -> bool:
5661 return result .skipped or has_failed_checks or len (result .cleanup_status .failed ) > 0
5762
5863
64+ def _run_recipe_impl (
65+ recipe_call : Callable [[], RecipeOutput | Awaitable [RecipeOutput ]],
66+ cleanup : _CleanupTracker ,
67+ cleanup_status : ExampleCleanupStatus ,
68+ ) -> ExampleResult :
69+ """Shared implementation for running recipes with cleanup."""
70+
71+ async def _run_async () -> RecipeOutput :
72+ try :
73+ result = recipe_call ()
74+ if asyncio .iscoroutine (result ):
75+ output : RecipeOutput = await result
76+ return output
77+ return result # type: ignore[return-value]
78+ finally :
79+ await cleanup .run ()
80+
81+ loop = asyncio .new_event_loop ()
82+ try :
83+ output = loop .run_until_complete (_run_async ())
84+ return ExampleResult (
85+ resources_created = output .resources_created ,
86+ checks = output .checks ,
87+ cleanup_status = cleanup_status ,
88+ )
89+ finally :
90+ loop .close ()
91+
92+
5993def wrap_recipe (
6094 recipe : Callable [[RecipeContext ], RecipeOutput ] | Callable [[RecipeContext ], Awaitable [RecipeOutput ]],
6195 validate_env : Callable [[], tuple [bool , list [ExampleCheck ]]] | None = None ,
@@ -72,7 +106,7 @@ def wrap_recipe(
72106 """
73107
74108 def run () -> ExampleResult :
75- cleanup_status = empty_cleanup_status ()
109+ cleanup_status = ExampleCleanupStatus ()
76110 cleanup = _CleanupTracker (cleanup_status )
77111
78112 if validate_env is not None :
@@ -86,27 +120,7 @@ def run() -> ExampleResult:
86120 )
87121
88122 ctx = RecipeContext (cleanup = cleanup )
89-
90- async def _run_async () -> RecipeOutput :
91- try :
92- result = recipe (ctx )
93- if asyncio .iscoroutine (result ):
94- output : RecipeOutput = await result
95- return output
96- return result # type: ignore[return-value]
97- finally :
98- await cleanup .run ()
99-
100- loop = asyncio .new_event_loop ()
101- try :
102- output = loop .run_until_complete (_run_async ())
103- return ExampleResult (
104- resources_created = output .resources_created ,
105- checks = output .checks ,
106- cleanup_status = cleanup_status ,
107- )
108- finally :
109- loop .close ()
123+ return _run_recipe_impl (lambda : recipe (ctx ), cleanup , cleanup_status )
110124
111125 return run
112126
@@ -127,7 +141,7 @@ def wrap_recipe_with_options(
127141 """
128142
129143 def run (options : T ) -> ExampleResult :
130- cleanup_status = empty_cleanup_status ()
144+ cleanup_status = ExampleCleanupStatus ()
131145 cleanup = _CleanupTracker (cleanup_status )
132146
133147 if validate_env is not None :
@@ -141,27 +155,7 @@ def run(options: T) -> ExampleResult:
141155 )
142156
143157 ctx = RecipeContext (cleanup = cleanup )
144-
145- async def _run_async () -> RecipeOutput :
146- try :
147- result = recipe (ctx , options )
148- if asyncio .iscoroutine (result ):
149- output : RecipeOutput = await result
150- return output
151- return result # type: ignore[return-value]
152- finally :
153- await cleanup .run ()
154-
155- loop = asyncio .new_event_loop ()
156- try :
157- output = loop .run_until_complete (_run_async ())
158- return ExampleResult (
159- resources_created = output .resources_created ,
160- checks = output .checks ,
161- cleanup_status = cleanup_status ,
162- )
163- finally :
164- loop .close ()
158+ return _run_recipe_impl (lambda : recipe (ctx , options ), cleanup , cleanup_status )
165159
166160 return run
167161
0 commit comments