litellm/litellm-rust/crates/host-python/tests/lifecycle.py
Yujong Lee 63d994ade4 refactor(rust): run OCR through a route-neutral callback contract and a legacy Logging adapter
Extracted from #41733 without the router loop, the cache machine layer, streaming, or the
error, timeout and route-pruning work that moved to #41745

litellm-callbacks holds the contract a native call and its host share: Machine, HostOp,
CallEvent, the in-process run loop, and Passthrough, which is built only by comparing the
caller's inputs with the body the route sends, so a route can never mark a key it rewrote.
litellm-host-python (formerly python-interop) owns the CPython driver and the Execution
handle, and litellm-callbacks-legacy is the @client wrapper as the native call sees it:
function_setup, the deployment hooks, pre_call and post_call, the success and failure fan-out
and the deferred proxy release. OCR is the one route on it, and the old core and bridge
lifecycles are gone

The passthrough rule is the structural fix for the bug #41719 patched in core and #41716
reworks: an inlined remote document no longer counts as the caller's value, so the legacy
adapter never hands the caller's URL back into the body. core/tests/ocr/passthrough.rs pins
it for every route and document source, including that unchanged values stay passthrough,
and callbacks-legacy/tests/payload.rs pins the adapter side with a real pre_call callback

Python OCR integration tests that only exercised core behavior now live as Rust tests, so
tests/test_litellm_rust keeps the cases that need the full Python stack
2026-09-17 21:13:16 -07:00

186 lines
5.1 KiB
Python

import asyncio
import gc
import threading
import weakref
from contextvars import ContextVar
async def exercise():
caller = asyncio.current_task()
thread = threading.get_ident()
loop = asyncio.get_running_loop()
marker = ContextVar("driver", default="before")
entered = asyncio.Event()
released = asyncio.Event()
result = object()
class CustomAwaitable:
def __await__(self):
return operation().__await__()
async def operation():
assert asyncio.current_task() is caller
assert threading.get_ident() == thread
assert asyncio.get_running_loop() is loop
marker.set("inside")
entered.set()
await released.wait()
assert asyncio.current_task() is caller
assert marker.get() == "inside"
return result
async def release():
await entered.wait()
released.set()
releaser = asyncio.create_task(release())
execution = await_execution(CustomAwaitable())
try:
execution.resume_value(None)
except RuntimeError:
pass
else:
raise AssertionError("resumed an unstarted execution")
wrapped = drive(execution)
try:
wrapped.send(1)
except TypeError:
pass
else:
raise AssertionError("accepted initial value")
assert await wrapped is result
assert marker.get() == "inside"
await releaser
execution.close()
execution.close()
try:
await wrapped
except RuntimeError:
pass
else:
raise AssertionError("accepted coroutine reuse")
final_awaitable = CustomAwaitable()
assert await drive(calling_execution(lambda: final_awaitable)) is final_awaitable
cause = KeyError("cause")
failure = ValueError("original")
async def failing():
await asyncio.sleep(0)
raise failure from cause
try:
await drive(await_execution(failing()))
except ValueError as error:
assert error is failure
assert error.__cause__ is cause
names = []
traceback = error.__traceback__
while traceback:
names.append(traceback.tb_frame.f_code.co_name)
traceback = traceback.tb_next
assert "failing" in names
else:
raise AssertionError("lost original exception")
for suppress in (False, True):
pending = asyncio.Event()
cleanup_entered = asyncio.Event()
cleanup_release = asyncio.Event()
cleaned = []
async def cancel_operation():
try:
pending.set()
await asyncio.Event().wait()
except asyncio.CancelledError:
if suppress:
return result
raise
finally:
cleanup_entered.set()
try:
await cleanup_release.wait()
except asyncio.CancelledError:
await cleanup_release.wait()
cleaned.append(asyncio.current_task())
task = asyncio.create_task(drive(await_execution(cancel_operation())))
await pending.wait()
task.cancel()
await cleanup_entered.wait()
assert not task.done()
task.cancel()
await asyncio.sleep(0)
cleanup_release.set()
if suppress:
assert await task is result
else:
try:
await task
except asyncio.CancelledError:
pass
else:
raise AssertionError("lost cancellation")
assert cleaned == [task]
observed = []
def reenter():
try:
active.start()
except RuntimeError as error:
observed.append(str(error))
return result
active = calling_execution(reenter)
assert await drive(active) is result
assert observed == ["execution is already running"]
class Finalizer:
def __call__(self):
return result
def __del__(self):
self.owner.close()
observed.append("released")
def cycle(started):
callback = Finalizer()
execution = calling_execution(callback)
callback.owner = execution
if started:
assert execution.start().value is result
return weakref.ref(callback)
for started in (False, True):
reference = cycle(started)
gc.collect()
assert reference() is None
assert observed[-2:] == ["released", "released"]
class Awaitable:
def __await__(self):
try:
yield self
finally:
observed.append("unwound")
def abandoned(started):
awaitable = Awaitable()
coroutine = drive(await_execution(awaitable))
awaitable.owner = coroutine
if started:
assert coroutine.send(None) is awaitable
coroutine.close()
return weakref.ref(awaitable)
for started in (False, True):
reference = abandoned(started)
gc.collect()
assert reference() is None
assert observed[-1] == "unwound"
asyncio.run(asyncio.wait_for(exercise(), 10))