mirror of
https://github.com/agentscope-ai/ReMe.git
synced 2026-08-28 05:25:04 +00:00
feat(utils): add universal timer decorator with loguru integration
This commit is contained in:
parent
da8260e5c0
commit
cb907ea201
4 changed files with 144 additions and 0 deletions
9
docs/deprecated.txt
Normal file
9
docs/deprecated.txt
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
from loguru import logger
|
||||
|
||||
用英文注释,完善module/class/function docstring,要一句话简洁,不要变更代码
|
||||
C0114: Missing module docstring (missing-module-docstring)
|
||||
C0115: Missing class docstring (missing-class-docstring)
|
||||
C0116: Missing function or method docstring (missing-function-docstring)
|
||||
done: { for f in ./*.py; do [[ "$f" != "./__init__.py" ]] && grep -v '^[[:space:]]*#' "$f"; done; } | pbcopy
|
||||
|
||||
然后是一个完整的tests,但是不要用其他的包,只是test开头的函数或者类,要求from loguru import logger
|
||||
5
reme_ai/core/utils/__init__.py
Normal file
5
reme_ai/core/utils/__init__.py
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
"""utils"""
|
||||
|
||||
from .timer import timer
|
||||
|
||||
__all__ = ["timer"]
|
||||
66
reme_ai/core/utils/timer.py
Normal file
66
reme_ai/core/utils/timer.py
Normal file
|
|
@ -0,0 +1,66 @@
|
|||
"""
|
||||
Utility module for timing function execution with log metadata preservation.
|
||||
"""
|
||||
|
||||
import functools
|
||||
import inspect
|
||||
import time
|
||||
from typing import Any, Callable, TypeVar, cast
|
||||
|
||||
from loguru import logger
|
||||
|
||||
# Type variable to preserve the signature of the decorated callable
|
||||
F = TypeVar("F", bound=Callable[..., Any])
|
||||
|
||||
|
||||
def timer(func: F) -> F:
|
||||
"""
|
||||
Decorator that logs execution time and patches log records with original function metadata.
|
||||
"""
|
||||
# Extract original function metadata to ensure logs point to the correct source
|
||||
func_name = func.__name__
|
||||
try:
|
||||
# Retrieve the source file path and the starting line number
|
||||
file_path = inspect.getsourcefile(func) or "unknown"
|
||||
_, line_no = inspect.getsourcelines(func)
|
||||
except Exception:
|
||||
file_path = "unknown"
|
||||
line_no = 0
|
||||
|
||||
def patcher(record):
|
||||
"""Modifies the log record to reflect the decorated function's location."""
|
||||
record["function"] = func_name
|
||||
record["file"].name = file_path.split("/")[-1]
|
||||
record["file"].path = file_path
|
||||
record["line"] = line_no
|
||||
|
||||
@functools.wraps(func)
|
||||
async def async_wrapper(*args: Any, **kwargs: Any) -> Any:
|
||||
"""Timer wrapper for asynchronous functions."""
|
||||
start_time = time.perf_counter()
|
||||
try:
|
||||
return await func(*args, **kwargs)
|
||||
finally:
|
||||
duration = time.perf_counter() - start_time
|
||||
# Use patch to inject metadata instead of relying on stack depth
|
||||
logger.patch(patcher).info(
|
||||
"========== cost={:.6f}s ==========",
|
||||
duration
|
||||
)
|
||||
|
||||
@functools.wraps(func)
|
||||
def sync_wrapper(*args: Any, **kwargs: Any) -> Any:
|
||||
"""Timer wrapper for synchronous functions."""
|
||||
start_time = time.perf_counter()
|
||||
try:
|
||||
return func(*args, **kwargs)
|
||||
finally:
|
||||
duration = time.perf_counter() - start_time
|
||||
logger.patch(patcher).info(
|
||||
"========== cost={:.6f}s ==========",
|
||||
duration
|
||||
)
|
||||
|
||||
if inspect.iscoroutinefunction(func):
|
||||
return cast(F, async_wrapper)
|
||||
return cast(F, sync_wrapper)
|
||||
64
tests/test_timer.py
Normal file
64
tests/test_timer.py
Normal file
|
|
@ -0,0 +1,64 @@
|
|||
"""
|
||||
This module provides a suite of tests to verify universal timer decorator functionality using loguru.
|
||||
"""
|
||||
|
||||
import asyncio
|
||||
import time
|
||||
|
||||
from loguru import logger
|
||||
|
||||
from reme_ai.core.utils import timer
|
||||
|
||||
|
||||
@timer
|
||||
def test_sync_function(seconds: float) -> str:
|
||||
"""Tests timing of a standard synchronous function."""
|
||||
time.sleep(seconds)
|
||||
return "sync done"
|
||||
|
||||
|
||||
@timer
|
||||
async def test_async_function(seconds: float) -> str:
|
||||
"""Tests timing of an asynchronous function."""
|
||||
await asyncio.sleep(seconds)
|
||||
return "async done"
|
||||
|
||||
|
||||
class TestMemberMethods:
|
||||
"""Container class to test class method decoration."""
|
||||
|
||||
@timer
|
||||
def test_sync_method(self, seconds: float) -> None:
|
||||
"""Tests a synchronous instance method."""
|
||||
time.sleep(seconds)
|
||||
|
||||
@timer
|
||||
async def test_async_method(self, seconds: float) -> None:
|
||||
"""Tests an asynchronous instance method."""
|
||||
await asyncio.sleep(seconds)
|
||||
|
||||
|
||||
def run_all_tests() -> None:
|
||||
"""
|
||||
Manual test runner.
|
||||
Notice that the logs will now point to the line numbers below
|
||||
(where the function is actually called).
|
||||
"""
|
||||
logger.info("Starting tests and verifying stack trace...")
|
||||
|
||||
# 1. Test Sync Function
|
||||
test_sync_function(0.1)
|
||||
|
||||
# 2. Test Async Function
|
||||
asyncio.run(test_async_function(0.1))
|
||||
|
||||
# 3. Test Class Methods
|
||||
tester = TestMemberMethods()
|
||||
tester.test_sync_method(0.05)
|
||||
asyncio.run(tester.test_async_method(0.05))
|
||||
|
||||
logger.success("All tests completed.")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
run_all_tests()
|
||||
Loading…
Add table
Reference in a new issue