fix: guard asyncio.create_task in RubrikLogger.__init__ against missing event loop

asyncio.create_task() raises RuntimeError when called outside a running
event loop. Wrap the call in a try/except RuntimeError so that RubrikLogger
can be instantiated in synchronous contexts (e.g. during startup, testing)
without crashing. The periodic_flush background task simply won't start in
those cases; it starts normally when the constructor is called inside an
event loop.

Add a test that verifies instantiation outside an event loop does not raise
(does not patch asyncio.create_task).

Co-authored-by: Sameer Kankute <Sameerlite@users.noreply.github.com>
This commit is contained in:
Cursor Agent 2026-05-04 13:31:39 +00:00
parent 8333cfd2f6
commit 021a14918b
No known key found for this signature in database
2 changed files with 26 additions and 17 deletions

View file

@ -121,7 +121,13 @@ class RubrikLogger(CustomGuardrail, CustomBatchLogger):
if self.key:
self._headers["Authorization"] = f"Bearer {self.key}"
asyncio.create_task(self.periodic_flush())
try:
asyncio.create_task(self.periodic_flush())
except RuntimeError:
verbose_logger.debug(
"Rubrik: no running event loop at init time; "
"periodic_flush will not run automatically."
)
async def aclose(self):
"""Close the dedicated tool blocking HTTP client."""

View file

@ -59,9 +59,7 @@ class TestInitialization:
def test_init_with_constructor_params(self):
with patch("asyncio.create_task", Mock()):
handler = RubrikLogger(
api_key="ctor-key", api_base="http://ctor-host:9090"
)
handler = RubrikLogger(api_key="ctor-key", api_base="http://ctor-host:9090")
assert handler.key == "ctor-key"
assert (
handler.tool_blocking_endpoint
@ -148,14 +146,23 @@ class TestInitialization:
):
assert RubrikLogger().batch_size == 256
def test_init_outside_event_loop_does_not_raise(self):
"""Instantiation without a running event loop must not raise RuntimeError."""
with patch.dict(
os.environ,
{"RUBRIK_WEBHOOK_URL": "http://localhost:8080", "RUBRIK_API_KEY": "k"},
):
# Do NOT patch asyncio.create_task — the real call should be
# guarded and fall back gracefully when there is no event loop.
handler = RubrikLogger()
assert handler.tool_blocking_endpoint.startswith("http://localhost:8080")
def test_headers_with_api_key(self, handler):
assert handler._headers["Authorization"] == "Bearer test-api-key"
assert handler._headers["Content-Type"] == "application/json"
def test_headers_without_api_key(self):
with patch.dict(
os.environ, {"RUBRIK_WEBHOOK_URL": "http://host"}, clear=True
):
with patch.dict(os.environ, {"RUBRIK_WEBHOOK_URL": "http://host"}, clear=True):
with patch("asyncio.create_task", Mock()):
h = RubrikLogger()
assert "Authorization" not in h._headers
@ -352,9 +359,7 @@ def _echo_service():
@pytest.mark.asyncio
class TestApplyGuardrail:
async def test_skips_requests(self, handler):
inputs = make_inputs_with_tools(
[make_tool_call_dict("call_1", "test_tool")]
)
inputs = make_inputs_with_tools([make_tool_call_dict("call_1", "test_tool")])
result = await handler.apply_guardrail(
inputs=inputs, request_data={}, input_type="request"
)
@ -465,9 +470,7 @@ class TestApplyGuardrail:
async def test_blocking_service_payload_format(self, handler):
tc1 = make_tool_call_dict("call_1", "get_weather", '{"location": "SF"}')
tc2 = make_tool_call_dict(
"call_2", "send_email", '{"to": "user@example.com"}'
)
tc2 = make_tool_call_dict("call_2", "send_email", '{"to": "user@example.com"}')
inputs = make_inputs_with_tools([tc1, tc2])
captured_payload: Dict[str, Any] = {}
@ -606,9 +609,7 @@ class TestApplyGuardrailAnthropicFormat:
inputs = make_inputs_with_tools([tc])
mock_client = AsyncMock()
mock_client.post = AsyncMock(
side_effect=httpx.TimeoutException("Timeout")
)
mock_client.post = AsyncMock(side_effect=httpx.TimeoutException("Timeout"))
handler.tool_blocking_client = mock_client
result = await handler.apply_guardrail(
@ -729,5 +730,7 @@ class TestResolveModel:
response = Mock()
response.model = ""
result = RubrikLogger._resolve_model({"response": response}, {"model": "fallback"})
result = RubrikLogger._resolve_model(
{"response": response}, {"model": "fallback"}
)
assert result == "unknown"