chore: 调整Python版本与依赖适配,新增性能报告测试与工具函数
1. 将Python版本从3.13降级到3.11 2. 为typing-extensions添加版本适配标记 3. 简化dev依赖组,移除pysnooper 4. 重构perf_timer,提取_generate_report独立函数 5. 新增性能报告生成与测试用例
This commit is contained in:
+1
-1
@@ -1 +1 @@
|
|||||||
3.13
|
3.11
|
||||||
|
|||||||
+2
-5
@@ -13,7 +13,7 @@ classifiers = [
|
|||||||
]
|
]
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"graphlib_backport >= 1.0.0; python_version < '3.9'",
|
"graphlib_backport >= 1.0.0; python_version < '3.9'",
|
||||||
"typing-extensions>=4.13.2",
|
"typing-extensions>=4.13.2; python_version < '3.10'",
|
||||||
]
|
]
|
||||||
description = "Lightweight, type-safe DAG task scheduler with multi-strategy execution."
|
description = "Lightweight, type-safe DAG task scheduler with multi-strategy execution."
|
||||||
keywords = ["async", "dag", "scheduler", "task", "workflow"]
|
keywords = ["async", "dag", "scheduler", "task", "workflow"]
|
||||||
@@ -93,10 +93,7 @@ packages = ["src/pyflowx"]
|
|||||||
pyflowx = { workspace = true }
|
pyflowx = { workspace = true }
|
||||||
|
|
||||||
[dependency-groups]
|
[dependency-groups]
|
||||||
dev = [
|
dev = ["pyflowx[dev,office,llm]"]
|
||||||
"pyflowx[dev,office,llm]",
|
|
||||||
"pysnooper>=1.2.3",
|
|
||||||
]
|
|
||||||
|
|
||||||
[tool.coverage.run]
|
[tool.coverage.run]
|
||||||
branch = true
|
branch = true
|
||||||
|
|||||||
+43
-6
@@ -1,15 +1,19 @@
|
|||||||
"""常用工具函数."""
|
"""常用工具函数."""
|
||||||
|
|
||||||
__all__ = ["perf_timer"]
|
from __future__ import annotations
|
||||||
|
|
||||||
|
__all__ = ["perf_timer"]
|
||||||
|
|
||||||
import functools
|
import functools
|
||||||
import logging
|
import logging
|
||||||
import time
|
import time
|
||||||
from collections import defaultdict
|
from collections import defaultdict
|
||||||
from typing import Callable, ParamSpec, TypedDict
|
from typing import Callable, TypedDict
|
||||||
|
|
||||||
from typing_extensions import TypeVar
|
try:
|
||||||
|
from typing_extensions import ParamSpec, TypeVar
|
||||||
|
except ImportError:
|
||||||
|
from typing import ParamSpec, TypeVar
|
||||||
|
|
||||||
P = ParamSpec("P")
|
P = ParamSpec("P")
|
||||||
R = TypeVar("R")
|
R = TypeVar("R")
|
||||||
@@ -30,6 +34,37 @@ _perf_metrics: defaultdict[str, _PerformanceMetrics] = defaultdict(
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def _generate_report(unit: str, precision: int) -> str:
|
||||||
|
"""生成性能指标报告,返回报告字符串."""
|
||||||
|
if not _perf_metrics:
|
||||||
|
return ""
|
||||||
|
|
||||||
|
lines: list[str] = []
|
||||||
|
lines.append("=" * 50)
|
||||||
|
lines.append("性能指标报告 (Performance Metrics Report)")
|
||||||
|
lines.append("-" * 50)
|
||||||
|
|
||||||
|
# 按总耗时排序,最耗时的函数排在前面
|
||||||
|
sorted_metrics = sorted(_perf_metrics.items(), key=lambda x: x[1]["total_time"], reverse=True)
|
||||||
|
|
||||||
|
for name, metrics in sorted_metrics:
|
||||||
|
avg_time = metrics["total_time"] / metrics["count"] if metrics["count"] > 0 else 0
|
||||||
|
lines.append(
|
||||||
|
f"{name}: "
|
||||||
|
f"调用次数={metrics['count']}, "
|
||||||
|
f"总耗时={metrics['total_time']:.{precision}f}{unit}, "
|
||||||
|
f"平均耗时={avg_time:.{precision}f}{unit}"
|
||||||
|
)
|
||||||
|
|
||||||
|
lines.append("=" * 50)
|
||||||
|
report_str = "\n".join(lines)
|
||||||
|
|
||||||
|
# 同时输出到日志
|
||||||
|
logging.info("\n".join(lines))
|
||||||
|
|
||||||
|
return report_str
|
||||||
|
|
||||||
|
|
||||||
def perf_timer(unit: str = "ms", precision: int = 4, report: bool = False):
|
def perf_timer(unit: str = "ms", precision: int = 4, report: bool = False):
|
||||||
"""性能计时器装饰器."""
|
"""性能计时器装饰器."""
|
||||||
scale: dict[str, float] = {
|
scale: dict[str, float] = {
|
||||||
@@ -63,8 +98,10 @@ def perf_timer(unit: str = "ms", precision: int = 4, report: bool = False):
|
|||||||
logging.info(f"Performance metrics report enabled with unit {unit} and precision {precision}")
|
logging.info(f"Performance metrics report enabled with unit {unit} and precision {precision}")
|
||||||
|
|
||||||
@atexit.register
|
@atexit.register
|
||||||
def _() -> None:
|
def _report_at_exit() -> None:
|
||||||
for name, metrics in _perf_metrics.items():
|
"""在程序退出时报告性能指标."""
|
||||||
logging.info(f"{name}: {metrics['count']} times, {metrics['total_time']:.{precision}f}{unit}")
|
_generate_report(unit, precision)
|
||||||
|
|
||||||
|
# 将报告生成逻辑提取为独立函数,便于测试
|
||||||
|
|
||||||
return decorator
|
return decorator
|
||||||
|
|||||||
@@ -39,3 +39,27 @@ class TestPerformanceTimer:
|
|||||||
assert _perf_metrics["test_func"]["total_time"] >= 0.1
|
assert _perf_metrics["test_func"]["total_time"] >= 0.1
|
||||||
|
|
||||||
assert mock_log.call_count == 1
|
assert mock_log.call_count == 1
|
||||||
|
|
||||||
|
def test_generate_report(self, mocker: MockerFixture, caplog: pytest.LogCaptureFixture):
|
||||||
|
mock_log = mocker.patch("logging.info")
|
||||||
|
|
||||||
|
from pyflowx.utils import _generate_report
|
||||||
|
|
||||||
|
@perf_timer(report=True, unit="ms", precision=3)
|
||||||
|
def test_func():
|
||||||
|
time.sleep(0.1)
|
||||||
|
|
||||||
|
@perf_timer(report=True, unit="ms", precision=3)
|
||||||
|
def test_func2():
|
||||||
|
time.sleep(0.2)
|
||||||
|
|
||||||
|
test_func()
|
||||||
|
test_func2()
|
||||||
|
|
||||||
|
_generate_report("ms", 3)
|
||||||
|
|
||||||
|
assert mock_log.call_count == 3
|
||||||
|
assert _perf_metrics["test_func"]["count"] == 1
|
||||||
|
assert _perf_metrics["test_func"]["total_time"] >= 0.1
|
||||||
|
assert _perf_metrics["test_func2"]["count"] == 1
|
||||||
|
assert _perf_metrics["test_func2"]["total_time"] >= 0.2
|
||||||
|
|||||||
@@ -5607,8 +5607,6 @@ version = "0.2.10"
|
|||||||
source = { editable = "." }
|
source = { editable = "." }
|
||||||
dependencies = [
|
dependencies = [
|
||||||
{ name = "graphlib-backport", marker = "python_full_version < '3.9'" },
|
{ name = "graphlib-backport", marker = "python_full_version < '3.9'" },
|
||||||
{ name = "typing-extensions", version = "4.13.2", source = { registry = "https://mirrors.aliyun.com/pypi/simple/" }, marker = "python_full_version < '3.9'" },
|
|
||||||
{ name = "typing-extensions", version = "4.15.0", source = { registry = "https://mirrors.aliyun.com/pypi/simple/" }, marker = "python_full_version >= '3.9'" },
|
|
||||||
]
|
]
|
||||||
|
|
||||||
[package.optional-dependencies]
|
[package.optional-dependencies]
|
||||||
@@ -5682,7 +5680,7 @@ requires-dist = [
|
|||||||
{ name = "sglang", extras = ["all"], marker = "python_full_version >= '3.10' and sys_platform == 'linux' and extra == 'llm'", specifier = "==0.5.10rc0" },
|
{ name = "sglang", extras = ["all"], marker = "python_full_version >= '3.10' and sys_platform == 'linux' and extra == 'llm'", specifier = "==0.5.10rc0" },
|
||||||
{ name = "tox", marker = "extra == 'dev'", specifier = ">=4.25.0" },
|
{ name = "tox", marker = "extra == 'dev'", specifier = ">=4.25.0" },
|
||||||
{ name = "tox-uv", marker = "extra == 'dev'", specifier = ">=1.13.1" },
|
{ name = "tox-uv", marker = "extra == 'dev'", specifier = ">=1.13.1" },
|
||||||
{ name = "typing-extensions", specifier = ">=4.13.2" },
|
{ name = "typing-extensions", marker = "python_full_version < '3.8'", specifier = ">=4.13.2" },
|
||||||
]
|
]
|
||||||
provides-extras = ["dev", "llm", "office"]
|
provides-extras = ["dev", "llm", "office"]
|
||||||
|
|
||||||
@@ -8439,22 +8437,13 @@ name = "typing-extensions"
|
|||||||
version = "4.15.0"
|
version = "4.15.0"
|
||||||
source = { registry = "https://mirrors.aliyun.com/pypi/simple/" }
|
source = { registry = "https://mirrors.aliyun.com/pypi/simple/" }
|
||||||
resolution-markers = [
|
resolution-markers = [
|
||||||
"python_full_version >= '3.15' and sys_platform == 'darwin'",
|
|
||||||
"python_full_version >= '3.15' and platform_machine == 'aarch64' and sys_platform == 'linux'",
|
"python_full_version >= '3.15' and platform_machine == 'aarch64' and sys_platform == 'linux'",
|
||||||
"python_full_version >= '3.15' and sys_platform == 'win32'",
|
|
||||||
"python_full_version >= '3.15' and sys_platform == 'emscripten'",
|
|
||||||
"(python_full_version >= '3.15' and platform_machine != 'aarch64' and sys_platform == 'linux') or (python_full_version >= '3.15' and sys_platform != 'darwin' and sys_platform != 'emscripten' and sys_platform != 'linux' and sys_platform != 'win32')",
|
"(python_full_version >= '3.15' and platform_machine != 'aarch64' and sys_platform == 'linux') or (python_full_version >= '3.15' and sys_platform != 'darwin' and sys_platform != 'emscripten' and sys_platform != 'linux' and sys_platform != 'win32')",
|
||||||
"python_full_version == '3.14.*' and sys_platform == 'darwin'",
|
|
||||||
"python_full_version == '3.13.*' and sys_platform == 'darwin'",
|
|
||||||
"python_full_version == '3.12.*' and sys_platform == 'darwin'",
|
"python_full_version == '3.12.*' and sys_platform == 'darwin'",
|
||||||
"python_full_version == '3.14.*' and platform_machine == 'aarch64' and sys_platform == 'linux'",
|
"python_full_version == '3.14.*' and platform_machine == 'aarch64' and sys_platform == 'linux'",
|
||||||
"python_full_version == '3.13.*' and platform_machine == 'aarch64' and sys_platform == 'linux'",
|
"python_full_version == '3.13.*' and platform_machine == 'aarch64' and sys_platform == 'linux'",
|
||||||
"python_full_version == '3.12.*' and platform_machine == 'aarch64' and sys_platform == 'linux'",
|
"python_full_version == '3.12.*' and platform_machine == 'aarch64' and sys_platform == 'linux'",
|
||||||
"python_full_version == '3.14.*' and sys_platform == 'win32'",
|
|
||||||
"python_full_version == '3.14.*' and sys_platform == 'emscripten'",
|
|
||||||
"(python_full_version == '3.14.*' and platform_machine != 'aarch64' and sys_platform == 'linux') or (python_full_version == '3.14.*' and sys_platform != 'darwin' and sys_platform != 'emscripten' and sys_platform != 'linux' and sys_platform != 'win32')",
|
"(python_full_version == '3.14.*' and platform_machine != 'aarch64' and sys_platform == 'linux') or (python_full_version == '3.14.*' and sys_platform != 'darwin' and sys_platform != 'emscripten' and sys_platform != 'linux' and sys_platform != 'win32')",
|
||||||
"python_full_version == '3.13.*' and sys_platform == 'win32'",
|
|
||||||
"python_full_version == '3.13.*' and sys_platform == 'emscripten'",
|
|
||||||
"(python_full_version == '3.13.*' and platform_machine != 'aarch64' and sys_platform == 'linux') or (python_full_version == '3.13.*' and sys_platform != 'darwin' and sys_platform != 'emscripten' and sys_platform != 'linux' and sys_platform != 'win32')",
|
"(python_full_version == '3.13.*' and platform_machine != 'aarch64' and sys_platform == 'linux') or (python_full_version == '3.13.*' and sys_platform != 'darwin' and sys_platform != 'emscripten' and sys_platform != 'linux' and sys_platform != 'win32')",
|
||||||
"python_full_version == '3.12.*' and sys_platform == 'win32'",
|
"python_full_version == '3.12.*' and sys_platform == 'win32'",
|
||||||
"python_full_version == '3.12.*' and sys_platform == 'emscripten'",
|
"python_full_version == '3.12.*' and sys_platform == 'emscripten'",
|
||||||
|
|||||||
Reference in New Issue
Block a user