Skip to content

Commit ca5c0b8

Browse files
committed
some deduping
1 parent c917d0b commit ca5c0b8

4 files changed

Lines changed: 41 additions & 67 deletions

File tree

examples/_harness.py

Lines changed: 39 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import sys
44
import json
5+
import time
56
import asyncio
67
from typing import Any, TypeVar, Callable, Awaitable
78
from dataclasses import asdict
@@ -13,12 +14,16 @@
1314
RecipeContext,
1415
ExampleCleanupStatus,
1516
ExampleCleanupFailure,
16-
empty_cleanup_status,
1717
)
1818

1919
T = 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+
2227
class _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+
5993
def 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

examples/devbox_from_blueprint_lifecycle.py

Lines changed: 1 addition & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -24,20 +24,12 @@
2424

2525
from __future__ import annotations
2626

27-
import time
28-
2927
from runloop_api_client import RunloopSDK
3028
from runloop_api_client.lib.polling import PollingConfig
3129

32-
from ._harness import run_as_cli, wrap_recipe
30+
from ._harness import run_as_cli, unique_name, wrap_recipe
3331
from .example_types import ExampleCheck, RecipeOutput, RecipeContext
3432

35-
36-
def unique_name(prefix: str) -> str:
37-
"""Generate a unique name with timestamp and random suffix."""
38-
return f"{prefix}-{int(time.time())}-{hex(int(time.time() * 1000) % 0xFFFFFF)[2:]}"
39-
40-
4133
BLUEPRINT_POLL_TIMEOUT_S = 10 * 60
4234

4335

examples/example_types.py

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -62,8 +62,3 @@ class RecipeContext:
6262
"""Context passed to recipe functions."""
6363

6464
cleanup: Any # CleanupTracker, but using Any to avoid circular typing issues
65-
66-
67-
def empty_cleanup_status() -> ExampleCleanupStatus:
68-
"""Create an empty cleanup status."""
69-
return ExampleCleanupStatus(attempted=[], succeeded=[], failed=[])

examples/mcp_github_tools.py

Lines changed: 1 addition & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -29,20 +29,13 @@
2929
from __future__ import annotations
3030

3131
import os
32-
import time
3332
from dataclasses import dataclass
3433

3534
from runloop_api_client import RunloopSDK
3635

37-
from ._harness import run_as_cli, wrap_recipe_with_options
36+
from ._harness import run_as_cli, unique_name, wrap_recipe_with_options
3837
from .example_types import ExampleCheck, RecipeOutput, RecipeContext
3938

40-
41-
def unique_name(prefix: str) -> str:
42-
"""Generate a unique name with timestamp and random suffix."""
43-
return f"{prefix}-{int(time.time())}-{hex(int(time.time() * 1000) % 0xFFFFFF)[2:]}"
44-
45-
4639
GITHUB_MCP_ENDPOINT = "https://api.githubcopilot.com/mcp/"
4740

4841

0 commit comments

Comments
 (0)