Metadata-Version: 2.5
Name: job-progress-report
Version: 0.2.2
Summary: 独立的作业进度上报工具包，通过写 JSON 文件的方式上报进度，与任何调度/管理平台解耦
Project-URL: Homepage, https://github.com/diosguo/job-progress-report
Project-URL: Repository, https://github.com/diosguo/job-progress-report
Author: diosguo
License: MIT
Keywords: job,monitoring,progress,report
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.8
Classifier: Programming Language :: Python :: 3.9
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Requires-Python: >=3.8
Description-Content-Type: text/markdown

# job-progress-report

独立的作业进度上报工具包，通过写 JSON 文件的方式上报进度，与任何调度/管理平台解耦。

## 特性

- **零第三方依赖**：仅使用 Python 标准库，Python ≥ 3.8
- **原子写入**：先写临时文件再 `os.replace`，保证外部读取者不会读到半截文件
- **线程安全**：进程内使用 `threading.Lock` 保护
- **进程安全**：`step()` 函数使用文件锁（`flock`），支持多进程/多线程并发递增
- **写缓存（Throttle）**：高频写入时自动合并到内存，每间隔 N 秒自动写入一次磁盘，减少 IO 开销
- **默认启用**：无任何环境变量时，默认输出到当前工作目录下的 `.job_progress.json`
- **不打断业务**：写文件失败默认静默吞掉（可配置严格模式抛异常）

## 安装

```bash
pip install job-progress-report
```

## 快速开始

### Python 使用

```python
from job_progress_report import report, finish, Progress

# 简单上报
report(stage="初始化", message="读取输入")

# 循环中上报进度
for i, item in enumerate(items):
    report(current=i + 1, total=len(items), stage="处理中", message=str(item))

# 结束
finish(success=True, message="完成")

# 使用上下文管理器（推荐）
with Progress(total=1000) as p:
    for i in range(1000):
        do_work()
        p.step(message=f"第 {i+1} 个")
```

### 多进程/多线程并发递增

```python
from job_progress_report import report, step, finish
from concurrent.futures import ThreadPoolExecutor

# 主进程初始化 total
report(current=0, total=1000, stage="处理中")

# 子线程/子进程中使用 step() 安全递增
def worker():
    for _ in range(100):
        step()  # 线程/进程安全，自动读取 current 并 +1

with ThreadPoolExecutor(max_workers=10) as executor:
    for _ in range(10):
        executor.submit(worker)

finish(success=True)
```

### 命令行使用

```bash
# 上报进度
job-progress-report report --progress 42 --current 210 --total 500 --stage 对接

# 合并更新
job-progress-report update --stage 对接 --message "处理中"

# 线程/进程安全递增
job-progress-report step --n 5 --message "处理中"

# 结束
job-progress-report finish --success

# 标记失败
job-progress-report finish --fail

# 删除进度文件
job-progress-report clear

# 查看进度文件路径
job-progress-report path
```

## 进度文件格式

```json
{
  "schema": 1,
  "progress": 42.0,
  "current": 210,
  "total": 500,
  "message": "正在处理 210/500",
  "stage": "对接",
  "stage_started_at": "2026-08-14T03:00:00Z",
  "update": "",
  "status": "running",
  "updated_at": 1755000000
}
```

| 字段 | 类型 | 说明 |
|------|------|------|
| `schema` | int | 格式版本号，当前为 `1` |
| `progress` | float | 进度百分比，范围 `[0, 100]` |
| `current` | int | 当前进度值 |
| `total` | int | 总进度值 |
| `message` | string | 人类可读的进度详情 |
| `stage` | string | 当前阶段名称 |
| `stage_started_at` | string | 当前阶段开始时间（UTC ISO 8601），传入 `stage` 时自动记录 |
| `update` | string | 更新说明 |
| `status` | string | 作业状态：`running` / `succeeded` / `failed` |
| `updated_at` | int | 最后更新时间（Unix 时间戳） |

## 环境变量配置

| 环境变量 | 说明 |
|---------|------|
| `JOB_PROGRESS_DISABLED=1` | 强制禁用进度上报 |
| `JOB_PROGRESS_FILE` | 完整的 JSON 文件绝对路径 |
| `JOB_PROGRESS_DIR` | 进度文件所在目录 |
| `JOB_PROGRESS_FILENAME` | 进度文件名（默认 `.job_progress.json`） |
| `JOB_PROGRESS_STRICT=1` | 严格模式：写文件失败时抛异常 |
| `JOB_PROGRESS_DEBOUNCE` | 写缓存间隔（秒），默认 `10`，设为 `0` 禁用

**默认行为**：无任何环境变量时，进度写入当前工作目录下的 `.job_progress.json`。

## API 参考

### 模块级函数

- `report(progress, current, total, message, stage, update, status, file)` - 写入进度快照
- `update(**kwargs)` - 合并式更新进度文件
- `step(n=1, message, file)` - 线程/进程安全地递增 current（使用文件锁）
- `finish(success=True, message, stage, file)` - 结束进度
- `clear(file)` - 删除进度文件
- `is_enabled()` - 是否已启用
- `resolve_file(explicit)` - 解析目标文件路径

### Progress 类

```python
class Progress:
    def __init__(self, total=None, file=None, auto_flush=True, auto_finish=True)
    def step(n=1, message=None)        # current += n，写盘
    def set_current(n, message=None)   # 显式设 current
    def report(**kwargs)               # 透传 report()
    def finish(success=True, message=None)
```

## 许可证

MIT