diff --git a/docs/deprecated.txt b/docs/deprecated.txt new file mode 100644 index 00000000..41d9c205 --- /dev/null +++ b/docs/deprecated.txt @@ -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 \ No newline at end of file diff --git a/reme_ai/core/utils/__init__.py b/reme_ai/core/utils/__init__.py new file mode 100644 index 00000000..bacf342f --- /dev/null +++ b/reme_ai/core/utils/__init__.py @@ -0,0 +1,5 @@ +"""utils""" + +from .timer import timer + +__all__ = ["timer"] diff --git a/reme_ai/core/utils/timer.py b/reme_ai/core/utils/timer.py new file mode 100644 index 00000000..4660ac2f --- /dev/null +++ b/reme_ai/core/utils/timer.py @@ -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) diff --git a/tests/test_timer.py b/tests/test_timer.py new file mode 100644 index 00000000..c9714e38 --- /dev/null +++ b/tests/test_timer.py @@ -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()