bump version to 0.2.7
This commit is contained in:
+4
-2
@@ -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
|
||||
|
||||
@@ -95,7 +95,7 @@ from .task import (
|
||||
task_template,
|
||||
)
|
||||
|
||||
__version__ = "0.3.0"
|
||||
__version__ = "0.3.1"
|
||||
|
||||
__all__ = [
|
||||
"IS_LINUX",
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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:
|
||||
return f"条件不满足: {', '.join(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
@@ -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
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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] = []
|
||||
|
||||
@@ -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,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
|
||||
|
||||
Reference in New Issue
Block a user