bump version to 0.2.7
Release / Pre-release Check (push) Failing after 35s
Release / Build Artifacts (push) Has been skipped
Release / Publish to PyPI (push) Has been skipped
Release / Publish Release (push) Has been skipped

This commit is contained in:
2026-06-27 15:23:48 +08:00
parent 1e23c48efc
commit 2b3f4b82d3
10 changed files with 7736 additions and 298 deletions
+4 -2
View File
@@ -6,6 +6,7 @@ classifiers = [
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
"Programming Language :: Python :: 3.13",
"Programming Language :: Python :: 3.14",
"Programming Language :: Python :: 3.8",
"Programming Language :: Python :: 3.9",
"Topic :: Software Development :: Libraries :: Application Frameworks",
@@ -20,7 +21,7 @@ license = { text = "MIT" }
name = "pyflowx"
readme = "README.md"
requires-python = ">=3.8"
version = "0.2.6"
version = "0.2.7"
[project.scripts]
autofmt = "pyflowx.cli.autofmt:main"
@@ -65,6 +66,7 @@ dev = [
"tox-uv>=1.13.1",
"tox>=4.25.0",
]
llm = ["sglang[all]==0.5.10rc0; python_version >= '3.10'"]
office = [
"pillow>=10.4.0",
"pymupdf>=1.24.11",
@@ -90,7 +92,7 @@ packages = ["src/pyflowx"]
pyflowx = { workspace = true }
[dependency-groups]
dev = ["pyflowx[dev,office]"]
dev = ["pyflowx[dev,office,llm]"]
[tool.coverage.run]
branch = true
+1 -1
View File
@@ -95,7 +95,7 @@ from .task import (
task_template,
)
__version__ = "0.3.0"
__version__ = "0.3.1"
__all__ = [
"IS_LINUX",
+21 -13
View File
@@ -4,19 +4,23 @@ import argparse
from pathlib import Path
import pyflowx as px
from pyflowx.conditions import BuiltinConditions
from pyflowx.conditions import BuiltinConditions, Constants
def main():
parser = argparse.ArgumentParser(description="Run a local model using SGLang.")
parser.add_argument("name", help="Model name.")
parser.add_argument("--dir", default=None, help="Directory of model.")
parser = argparse.ArgumentParser(description="启动 SGLang 服务")
parser.add_argument("--model", default="~/.models/Qwen2.5-Coder-32B-Instruct-AWQ", help="模型路径")
parser.add_argument("--port", type=int, default=8000, help="服务端口")
parser.add_argument("--ctx-len", type=int, default=28672, help="最大上下文长度")
parser.add_argument("--mem", type=float, default=0.75, help="显存占比 (0-1)")
parser.add_argument("--host", default="0.0.0.0", help="主机地址")
parser.add_argument("--log-level", default="info", help="日志级别")
args = parser.parse_args()
if not args.name:
parser.error("name is required")
if not args.model:
parser.error("model is required")
model_dir = Path(args.dir) if args.dir else Path.home() / ".models" / args.name.split("/")[-1]
model_dir = Path(args.model).expanduser()
if not model_dir.exists():
parser.error(f"Model directory {model_dir} does not exist.")
@@ -34,22 +38,26 @@ def main():
px.TaskSpec(
name="run",
cmd=[
"uvx",
"sglang",
"serve",
"python" if Constants.IS_WINDOWS else "python3",
"-m",
"sglang.launch_server",
"--model-path",
str(model_dir),
"--host",
"0.0.0.0",
str(args.host),
"--port",
"8000",
"--mem-fraction-static",
"0.88",
str(args.mem),
"--context-length",
"32768",
"--tool-call-parser",
"qwen",
"--log-level",
str(args.log_level),
],
verbose=True,
),
])
px.run(graph, verbose=True)
px.run(graph, strategy="sequential", verbose=True)
+2
View File
@@ -134,7 +134,9 @@ def _evaluate_conditions(spec: TaskSpec[Any], context: Mapping[str, Any]) -> str
failed_conditions.append(getattr(condition, "__name__", None) or "匿名条件")
if failed_conditions:
if len(failed_conditions) <= 2:
return f"条件不满足: {', '.join(failed_conditions)}"
return f"条件不满足: {', '.join(failed_conditions[:2])}{len(failed_conditions)}个条件"
if spec.skip_if_missing and not spec._is_cmd_available():
cmd_name = spec.cmd[0] if isinstance(spec.cmd, list) and spec.cmd else "unknown"
+33 -35
View File
@@ -35,8 +35,6 @@ from typing import (
Iterator,
List,
Mapping,
Optional,
Tuple,
Union,
cast,
)
@@ -107,7 +105,7 @@ class RetryPolicy:
delay: float = 0.0
backoff: float = 1.0
jitter: float = 0.0
retry_on: Tuple[type[BaseException], ...] = (Exception,)
retry_on: tuple[type[BaseException], ...] = (Exception,)
def __post_init__(self) -> None:
if self.max_attempts < 1:
@@ -151,9 +149,9 @@ class TaskHooks:
钩子异常不会影响任务状态,仅记录日志。
"""
pre_run: Optional[Callable[[TaskSpec[Any]], None]] = None
post_run: Optional[Callable[[TaskSpec[Any], Any], None]] = None
on_failure: Optional[Callable[[TaskSpec[Any], BaseException], None]] = None
pre_run: Callable[[TaskSpec[Any]], None] | None = None
post_run: Callable[[TaskSpec[Any], Any], None] | None = None
on_failure: Callable[[TaskSpec[Any], BaseException], None] | None = None
class TaskStatus(Enum):
@@ -248,27 +246,27 @@ class TaskSpec(Generic[T]):
"""
name: str
fn: Optional[TaskFn[T]] = None
cmd: Optional[TaskCmd] = None
depends_on: Tuple[str, ...] = ()
soft_depends_on: Tuple[str, ...] = ()
fn: TaskFn[T] | None = None
cmd: TaskCmd | None = None
depends_on: tuple[str, ...] = ()
soft_depends_on: tuple[str, ...] = ()
defaults: Mapping[str, Any] = field(default_factory=dict)
args: Tuple[Any, ...] = ()
args: tuple[Any, ...] = ()
kwargs: Mapping[str, Any] = field(default_factory=dict)
retry: RetryPolicy = field(default_factory=RetryPolicy)
timeout: Optional[float] = None
tags: Tuple[str, ...] = ()
conditions: Tuple[Condition, ...] = ()
cwd: Optional[Path] = None
env: Optional[Mapping[str, str]] = None
timeout: float | None = None
tags: tuple[str, ...] = ()
conditions: tuple[Condition, ...] = ()
cwd: Path | None = None
env: Mapping[str, str] | None = None
verbose: bool = False
skip_if_missing: bool = False
allow_upstream_skip: bool = False
strategy: Optional[str] = None
strategy: str | None = None
priority: int = 0
concurrency_key: Optional[str] = None
concurrency_key: str | None = None
continue_on_error: bool = False
cache_key: Optional[CacheKeyFn] = None
cache_key: CacheKeyFn | None = None
hooks: TaskHooks = field(default_factory=TaskHooks)
def __post_init__(self) -> None:
@@ -310,7 +308,7 @@ class TaskSpec(Generic[T]):
_run.__name__ = spec.name
return _run # type: ignore[return-value]
def should_execute(self, context: Context) -> Tuple[bool, Optional[str]]:
def should_execute(self, context: Context) -> tuple[bool, str | None]:
"""检查任务是否应执行。
Returns
@@ -367,12 +365,12 @@ class TaskSpec(Generic[T]):
@contextmanager
def _env_and_cwd(
env: Optional[Mapping[str, str]],
cwd: Optional[Path],
env: Mapping[str, str] | None,
cwd: Path | None,
) -> Iterator[None]:
"""临时设置环境变量与工作目录。"""
saved_env: dict[str, str] = {}
saved_cwd: Optional[str] = None
saved_cwd: str | None = None
if env:
for k, v in env.items():
if k in os.environ:
@@ -431,7 +429,7 @@ def _run_command(spec: TaskSpec[Any]) -> Any: # noqa: PLR0912
print(f"[verbose] 工作目录: {cwd}", flush=True)
# 合并环境变量
run_env: Optional[dict[str, str]] = None
run_env: dict[str, str] | None = None
if env_override:
run_env = dict(os.environ)
run_env.update(env_override)
@@ -470,8 +468,8 @@ def _run_command(spec: TaskSpec[Any]) -> Any: # noqa: PLR0912
# 任务模板:批量生成相似 TaskSpec 的工厂
# ---------------------------------------------------------------------- #
def task_template(
fn: Optional[TaskFn[Any]] = None,
cmd: Optional[TaskCmd] = None,
fn: TaskFn[Any] | None = None,
cmd: TaskCmd | None = None,
**defaults: Any,
) -> Callable[..., TaskSpec[Any]]:
"""创建任务模板工厂。
@@ -505,15 +503,15 @@ class TaskResult(Generic[T]):
spec: TaskSpec[T]
status: TaskStatus = TaskStatus.PENDING
value: Optional[T] = None
error: Optional[BaseException] = None
value: T | None = None
error: BaseException | None = None
attempts: int = 0
started_at: Optional[datetime] = None
finished_at: Optional[datetime] = None
reason: Optional[str] = None # 跳过原因
started_at: datetime | None = None
finished_at: datetime | None = None
reason: str | None = None # 跳过原因
@property
def duration(self) -> Optional[float]:
def duration(self) -> float | None:
"""从开始到结束的耗时(秒),未开始/未结束则为 ``None``。"""
if self.started_at is None or self.finished_at is None:
return None
@@ -527,6 +525,6 @@ class TaskEvent:
task: str
status: TaskStatus
attempts: int = 0
error: Optional[str] = None
duration: Optional[float] = None
reason: Optional[str] = None
error: str | None = None
duration: float | None = None
reason: str | None = None
+1
View File
@@ -238,6 +238,7 @@ class TestPdfInfo:
class TestPdfOcr:
"""Test pdf_ocr function."""
@pytest.mark.slow
def test_pdf_ocr_file(self, tmp_path: Path) -> None:
"""Should OCR PDF."""
pytest.importorskip("fitz")
+1
View File
@@ -529,6 +529,7 @@ class TestDependencyDrivenScheduling:
class TestConcurrencyLimits:
"""测试并发限制:相同 concurrency_key 的任务串行执行。"""
@pytest.mark.slow
def test_concurrency_key_serializes_tasks(self) -> None:
"""相同 key 的任务不应并发执行。"""
running: list[int] = []
+1
View File
@@ -264,6 +264,7 @@ def test_skip_if_missing_with_fn_not_checked():
assert spec.should_execute({})[0] is True
@pytest.mark.slow
def test_skip_if_missing_with_empty_cmd_list():
"""skip_if_missing=True 时,空命令列表应返回 True(不检查)."""
spec = TaskSpec("test", cmd=[""], skip_if_missing=True)
+1 -1
View File
@@ -1,6 +1,6 @@
[tox]
isolated_build = true
envlist = py38, py39, py310, py311, py312, py313
envlist = py38, py39, py310, py311, py312, py313, py314
min_version = 4.0
requires = tox-uv
skipsdist = true
Generated
+7670 -245
View File
File diff suppressed because it is too large Load Diff