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:
2026-06-27 20:47:29 +08:00
parent d58fc5536e
commit b62a544569
5 changed files with 71 additions and 24 deletions
+1 -1
View File
@@ -1 +1 @@
3.13 3.11
+2 -5
View File
@@ -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
View File
@@ -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
+24
View File
@@ -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
Generated
+1 -12
View File
@@ -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'",