vibego 0.2.52__py3-none-any.whl → 1.0.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of vibego might be problematic. Click here for more details.

project_repository.py CHANGED
@@ -1,9 +1,11 @@
1
- """项目配置仓库,负责在 SQLite JSON 间同步。
2
-
3
- 该模块承担以下职责:
4
- 1. 初始化 SQLite 数据库并在首次运行时从 JSON 导入数据,同时保留原始 JSON 备份;
5
- 2. 提供项目增删改查接口,所有写操作都会回写最新 JSON 以兼容旧逻辑;
6
- 3. 提供 ProjectConfig 所需的数据结构,供 master 与其他脚本复用。
1
+ """Project configuration repository syncing SQLite and JSON storage.
2
+
3
+ Responsibilities:
4
+ 1. Initialise the SQLite database and import data from JSON on first run while
5
+ keeping a backup of the original JSON file.
6
+ 2. Provide CRUD helpers that always write the latest JSON for legacy
7
+ compatibility.
8
+ 3. Expose data structures consumed by the master process and related scripts.
7
9
  """
8
10
  from __future__ import annotations
9
11
 
@@ -23,7 +25,7 @@ logger = logging.getLogger(__name__)
23
25
 
24
26
  @dataclass(frozen=True)
25
27
  class ProjectRecord:
26
- """描述单个项目的配置数据。"""
28
+ """Represent the configuration data for a single project."""
27
29
 
28
30
  bot_name: str
29
31
  bot_token: str
@@ -34,7 +36,7 @@ class ProjectRecord:
34
36
  legacy_name: Optional[str]
35
37
 
36
38
  def to_dict(self) -> dict:
37
- """转换为 JSON 序列化所需的字典。"""
39
+ """Return a dictionary representation ready for JSON serialisation."""
38
40
  return {
39
41
  "bot_name": self.bot_name,
40
42
  "bot_token": self.bot_token,
@@ -47,21 +49,21 @@ class ProjectRecord:
47
49
 
48
50
 
49
51
  class ProjectRepository:
50
- """项目配置仓库,封装所有读写逻辑。"""
52
+ """Repository wrapper that encapsulates project configuration I/O."""
51
53
 
52
54
  def __init__(self, db_path: Path, json_path: Path):
53
- """初始化仓库并自动创建所需的文件与目录。"""
55
+ """Initialise the repository and ensure required files exist."""
54
56
 
55
- # 保存路径,确保目录存在
57
+ # Store paths and create parent directories.
56
58
  self.db_path = db_path
57
59
  self.json_path = json_path
58
60
  self.db_path.parent.mkdir(parents=True, exist_ok=True)
59
61
  self.json_path.parent.mkdir(parents=True, exist_ok=True)
60
- # 初始化数据库文件
62
+ # Prepare the database file.
61
63
  self._initialize()
62
64
 
63
65
  def _initialize(self) -> None:
64
- """初始化数据库,如果首次创建则执行 JSON 导入。"""
66
+ """Initialise the database and import JSON data on first launch."""
65
67
  first_create = not self.db_path.exists()
66
68
  with self._connect() as conn:
67
69
  conn.execute("PRAGMA foreign_keys = ON;")
@@ -83,27 +85,27 @@ class ProjectRepository:
83
85
  )
84
86
  if first_create:
85
87
  self._import_from_json()
86
- # 每次启动都执行数据修复,保证旧数据被规范化
88
+ # Repair legacy records on every startup to keep data normalised.
87
89
  self._repair_records()
88
- # 启动时始终导出一次,确保 JSON 与数据库一致
90
+ # Export once on startup so JSON mirrors the database.
89
91
  self._export_to_json(self.list_projects())
90
92
 
91
93
  def _connect(self) -> sqlite3.Connection:
92
- """创建数据库连接,统一启用行字典模式。"""
94
+ """Create a database connection with row dictionaries enabled."""
93
95
  conn = sqlite3.connect(str(self.db_path))
94
96
  conn.row_factory = sqlite3.Row
95
97
  return conn
96
98
 
97
99
  def _import_from_json(self) -> None:
98
- """首次初始化时从 JSON 迁移数据,并保留备份文件。"""
100
+ """Import data from JSON during the first initialisation and keep a backup."""
99
101
  if not self.json_path.exists():
100
102
  return
101
103
  try:
102
104
  raw = json.loads(self.json_path.read_text(encoding="utf-8"))
103
105
  except json.JSONDecodeError as exc:
104
- raise RuntimeError(f"解析 {self.json_path} 时失败: {exc}") from exc
106
+ raise RuntimeError(f"Failed to parse {self.json_path}: {exc}") from exc
105
107
  if not isinstance(raw, list):
106
- raise RuntimeError(f"{self.json_path} 内容必须是数组")
108
+ raise RuntimeError(f"The content of {self.json_path} must be an array.")
107
109
  records: List[ProjectRecord] = []
108
110
  for item in raw:
109
111
  if not isinstance(item, dict):
@@ -125,12 +127,12 @@ class ProjectRepository:
125
127
  shutil.copy2(self.json_path, backup_path)
126
128
 
127
129
  def _build_backup_path(self) -> Path:
128
- """构造 JSON 备份路径,带有时间戳避免覆盖。"""
130
+ """Build a timestamped JSON backup path to avoid overwriting files."""
129
131
  timestamp = time.strftime("%Y%m%d-%H%M%S")
130
132
  return self.json_path.with_suffix(self.json_path.suffix + f".{timestamp}.bak")
131
133
 
132
134
  def list_projects(self) -> List[ProjectRecord]:
133
- """读取全部项目配置。"""
135
+ """Return all project configurations."""
134
136
  with self._connect() as conn:
135
137
  cursor = conn.execute(
136
138
  """
@@ -144,7 +146,7 @@ class ProjectRepository:
144
146
  return [self._normalize_record_fields(self._row_to_record(row, normalize=False)) for row in rows]
145
147
 
146
148
  def get_by_slug(self, slug: str) -> Optional[ProjectRecord]:
147
- """根据 project_slug 查询项目(忽略大小写以兼容历史数据)。"""
149
+ """Look up a project by slug (case-insensitive for legacy data)."""
148
150
  slug = self._sanitize_slug(slug)
149
151
  with self._connect() as conn:
150
152
  cursor = conn.execute(
@@ -161,7 +163,7 @@ class ProjectRepository:
161
163
  return self._normalize_record_fields(self._row_to_record(row, normalize=False))
162
164
 
163
165
  def get_by_bot_name(self, bot_name: str) -> Optional[ProjectRecord]:
164
- """根据 bot 名查询项目。"""
166
+ """Look up a project by bot name."""
165
167
  bot_name = self._sanitize_bot_name(bot_name)
166
168
  with self._connect() as conn:
167
169
  cursor = conn.execute(
@@ -178,7 +180,7 @@ class ProjectRepository:
178
180
  return self._normalize_record_fields(self._row_to_record(row, normalize=False))
179
181
 
180
182
  def insert_project(self, record: ProjectRecord) -> None:
181
- """新增项目记录。"""
183
+ """Insert a new project record."""
182
184
  normalized = self._normalize_record_fields(record)
183
185
  with self._connect() as conn:
184
186
  conn.execute("BEGIN IMMEDIATE;")
@@ -203,7 +205,7 @@ class ProjectRepository:
203
205
  self._export_to_json(self.list_projects())
204
206
 
205
207
  def update_project(self, slug: str, record: ProjectRecord) -> None:
206
- """更新项目记录,slug 作为定位字段(匹配时忽略大小写)。"""
208
+ """Update a project using its slug (case-insensitive) as the identifier."""
207
209
  normalized_slug = self._sanitize_slug(slug)
208
210
  normalized = self._normalize_record_fields(record)
209
211
  with self._connect() as conn:
@@ -228,12 +230,12 @@ class ProjectRepository:
228
230
  )
229
231
  if cursor.rowcount == 0:
230
232
  conn.rollback()
231
- raise ValueError(f"未找到项目 {slug}")
233
+ raise ValueError(f"Project {slug} not found")
232
234
  conn.commit()
233
235
  self._export_to_json(self.list_projects())
234
236
 
235
237
  def delete_project(self, slug: str) -> None:
236
- """删除指定项目(匹配时忽略大小写)。"""
238
+ """Delete a project by slug (case-insensitive)."""
237
239
  normalized_slug = self._sanitize_slug(slug)
238
240
  with self._connect() as conn:
239
241
  conn.execute("BEGIN IMMEDIATE;")
@@ -243,12 +245,12 @@ class ProjectRepository:
243
245
  )
244
246
  if cursor.rowcount == 0:
245
247
  conn.rollback()
246
- raise ValueError(f"未找到项目 {slug}")
248
+ raise ValueError(f"Project {slug} not found")
247
249
  conn.commit()
248
250
  self._export_to_json(self.list_projects())
249
251
 
250
252
  def _bulk_upsert(self, records: Iterable[ProjectRecord]) -> None:
251
- """批量写入项目数据,用于初始化导入。"""
253
+ """Insert or update multiple project records, used for bootstrap imports."""
252
254
  with self._connect() as conn:
253
255
  conn.execute("BEGIN IMMEDIATE;")
254
256
  for record in records:
@@ -281,7 +283,7 @@ class ProjectRepository:
281
283
  conn.commit()
282
284
 
283
285
  def _normalize_int(self, value: Optional[object]) -> Optional[int]:
284
- """将输入转换为整数或 None,兼容字符串类型。"""
286
+ """Convert the input to an int or None, handling string representations."""
285
287
  if value is None:
286
288
  return None
287
289
  if isinstance(value, int):
@@ -291,28 +293,28 @@ class ProjectRepository:
291
293
  return None
292
294
 
293
295
  def _sanitize_bot_name(self, bot_name: str) -> str:
294
- """去除多余空白与前导 @,统一 bot 名格式。"""
296
+ """Trim whitespace and leading @ symbols to normalise bot names."""
295
297
  cleaned = (bot_name or "").strip()
296
298
  if cleaned.startswith("@"):
297
299
  cleaned = cleaned[1:]
298
300
  return cleaned.strip()
299
301
 
300
302
  def _sanitize_slug(self, slug: str) -> str:
301
- """复用 master 侧逻辑,将 slug 归一化为小写并替换非法字符。"""
303
+ """Normalise slugs to lowercase and replace illegal characters."""
302
304
  text = (slug or "").strip().lower()
303
305
  text = text.replace(" ", "-").replace("/", "-").replace("\\", "-")
304
306
  text = text.strip("-")
305
307
  return text or "project"
306
308
 
307
309
  def _sanitize_optional_text(self, value: Optional[str]) -> Optional[str]:
308
- """通用字符串清洗,空字符串回落为 None"""
310
+ """Clean optional text values and return None for empty strings."""
309
311
  if value is None:
310
312
  return None
311
313
  cleaned = value.strip()
312
314
  return cleaned or None
313
315
 
314
316
  def _normalize_record_fields(self, record: ProjectRecord) -> ProjectRecord:
315
- """返回字段已归一化的新记录,避免数据库遗留非法值。"""
317
+ """Return a normalised record to avoid persisting invalid values."""
316
318
  allowed_chat_id = self._normalize_int(record.allowed_chat_id)
317
319
  clean_bot = self._sanitize_bot_name(record.bot_name)
318
320
  slug_source = record.project_slug.strip() or clean_bot
@@ -331,7 +333,7 @@ class ProjectRepository:
331
333
  )
332
334
 
333
335
  def _row_to_record(self, row: sqlite3.Row, *, normalize: bool = True) -> ProjectRecord:
334
- """将数据库行转换为 ProjectRecord,可选是否立即归一化。"""
336
+ """Convert a database row into ProjectRecord, optionally normalising it."""
335
337
  record = ProjectRecord(
336
338
  bot_name=row["bot_name"],
337
339
  bot_token=row["bot_token"],
@@ -344,7 +346,7 @@ class ProjectRepository:
344
346
  return self._normalize_record_fields(record) if normalize else record
345
347
 
346
348
  def _repair_records(self) -> None:
347
- """启动时统一修复遗留数据,确保 slug/bot name 无不合法字符。"""
349
+ """Repair legacy data on startup to ensure slugs and bot names are valid."""
348
350
  with self._connect() as conn:
349
351
  conn.row_factory = sqlite3.Row
350
352
  cursor = conn.execute(
@@ -367,13 +369,13 @@ class ProjectRepository:
367
369
  existing_slug_id = slug_owner.get(normalized.project_slug)
368
370
  if existing_slug_id is not None and existing_slug_id != current_id:
369
371
  raise RuntimeError(
370
- f"项目 slug 归一化冲突: {normalized.project_slug} 已被记录 {existing_slug_id} 占用"
372
+ f"Slug normalisation conflict: {normalized.project_slug} is already used by record {existing_slug_id}"
371
373
  )
372
374
  slug_owner[normalized.project_slug] = current_id
373
375
  existing_bot_id = bot_owner.get(normalized.bot_name)
374
376
  if existing_bot_id is not None and existing_bot_id != current_id:
375
377
  raise RuntimeError(
376
- f"bot 名归一化冲突: {normalized.bot_name} 已被记录 {existing_bot_id} 占用"
378
+ f"Bot name normalisation conflict: {normalized.bot_name} is already used by record {existing_bot_id}"
377
379
  )
378
380
  bot_owner[normalized.bot_name] = current_id
379
381
  if normalized != record:
@@ -402,10 +404,10 @@ class ProjectRepository:
402
404
  ),
403
405
  )
404
406
  conn.commit()
405
- logger.info("已修复 %s 条项目配置,统一 slug/bot 名格式", len(updates))
407
+ logger.info("Repaired %s project records and normalised slug/bot formats", len(updates))
406
408
 
407
409
  def _export_to_json(self, records: Iterable[ProjectRecord]) -> None:
408
- """将数据库内容导出为 JSON,兼容旧逻辑并保留易读格式。"""
410
+ """Export the database content to JSON, keeping it readable for legacy flows."""
409
411
  payload = [record.to_dict() for record in records]
410
412
  tmp_path = self.json_path.with_suffix(self.json_path.suffix + ".tmp")
411
413
  tmp_path.write_text(json.dumps(payload, ensure_ascii=False, indent=2), encoding="utf-8")
scripts/__init__.py CHANGED
@@ -1,2 +1 @@
1
- """脚本模块标记,便于打包工具识别此目录。"""
2
-
1
+ """Marker module so packaging tools treat this directory as a package."""
scripts/bump_version.sh CHANGED
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env bash
2
- # 版本管理便捷脚本
3
- # 使用方式:
2
+ # Convenient script for version management
3
+ # How to use:
4
4
  # ./scripts/bump_version.sh patch
5
5
  # ./scripts/bump_version.sh minor
6
6
  # ./scripts/bump_version.sh major
@@ -9,81 +9,83 @@
9
9
 
10
10
  set -e
11
11
 
12
- # 项目根目录
12
+ # Project root directory
13
13
  PROJECT_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
14
14
  cd "$PROJECT_ROOT"
15
15
 
16
- # bump-my-version 路径
17
- BUMP_CMD="/Users/david/.config/vibego/runtime/.venv/bin/bump-my-version"
16
+ # bump-my-version path
17
+ MASTER_CONFIG_ROOT="${MASTER_CONFIG_ROOT:-$HOME/.config/vibego}"
18
+ RUNTIME_DIR="${VIBEGO_RUNTIME_ROOT:-$MASTER_CONFIG_ROOT/runtime}"
19
+ BUMP_CMD="$RUNTIME_DIR/.venv/bin/bump-my-version"
18
20
 
19
- # 检查 bump-my-version 是否存在
21
+ # Check if bump-my-version exists
20
22
  if [ ! -f "$BUMP_CMD" ]; then
21
- echo "错误:找不到 bump-my-version"
22
- echo "请先安装:pip install bump-my-version"
23
+ echo "Error: bump-my-version not found"
24
+ echo "Please install first: pip install bump-my-version"
23
25
  exit 1
24
26
  fi
25
27
 
26
- # 如果没有参数,显示帮助
28
+ # If there are no parameters, display help
27
29
  if [ $# -eq 0 ]; then
28
- echo "用法:"
29
- echo " $0 patch 递增补丁版本 (0.2.11 → 0.2.12)"
30
- echo " 自动提交:fix: bugfixes"
31
- echo " $0 minor 递增次版本 (0.2.11 → 0.3.0)"
32
- echo " 自动提交:feat: 添加新功能"
33
- echo " $0 major 递增主版本 (0.2.11 → 1.0.0)"
34
- echo " 自动提交:feat!: 重大变更"
35
- echo " $0 show 显示当前版本"
36
- echo " $0 --dry-run 预览变更(添加在 patch/minor/major 后)"
30
+ echo "usage:"
31
+ echo " $0 patch Increment patch version (0.2.11 → 0.2.12)"
32
+ echo " Autocommit: fix: bugfixes"
33
+ echo " $0 minor Increment minor version (0.2.11 → 0.3.0)"
34
+ echo " Automatic submission: feat: Add new features"
35
+ echo " $0 major Increment major version (0.2.11 → 1.0.0)"
36
+ echo " Automatic submission: feat!: Major changes"
37
+ echo " $0 show Show current version"
38
+ echo " $0 --dry-run Preview changes (added in patch/minor/major back)"
37
39
  echo ""
38
- echo "说明:"
39
- echo " 脚本会自动提交当前未提交的修改,然后递增版本号。"
40
- echo " 如果不想自动提交,请在参数中添加 --no-auto-commit"
40
+ echo "illustrate:"
41
+ echo " The script automatically commits the currently uncommitted changes and then increments the version number."
42
+ echo " If you don't want to commit automatically, add --no-auto-commit to the parameters"
41
43
  echo ""
42
- echo "示例:"
43
- echo " $0 patch # 自动提交修改并递增补丁版本"
44
- echo " $0 patch --dry-run # 预览补丁版本递增(不会提交)"
45
- echo " $0 minor --no-auto-commit # 仅递增版本,不自动提交当前修改"
44
+ echo "Example:"
45
+ echo " $0 patch # Automatically commit changes andIncrement patch version"
46
+ echo " $0 patch --dry-run # The preview patch version is incremented (will not be submitted)"
47
+ echo " $0 minor --no-auto-commit # Only increments version, does not automatically commit current modifications"
46
48
  exit 0
47
49
  fi
48
50
 
49
- # 处理 show 命令
51
+ # Handling show commands
50
52
  if [ "$1" = "show" ]; then
51
53
  "$BUMP_CMD" show current_version
52
54
  exit 0
53
55
  fi
54
56
 
55
- # 处理 --help
57
+ # Processing --help
56
58
  if [ "$1" = "--help" ] || [ "$1" = "-h" ]; then
57
59
  "$BUMP_CMD" --help
58
60
  exit 0
59
61
  fi
60
62
 
61
- # 检查是否禁用自动提交
63
+ # Check if autocommit is disabled
62
64
  AUTO_COMMIT=true
63
65
  if [[ "$*" =~ "--no-auto-commit" ]]; then
64
66
  AUTO_COMMIT=false
65
67
  fi
66
68
 
67
- # 检查是否是 dry-run
69
+ # Check if it is dry-run
68
70
  DRY_RUN=false
69
71
  if [[ "$*" =~ "--dry-run" ]]; then
70
72
  DRY_RUN=true
71
73
  fi
72
74
 
73
- # 获取版本类型
75
+ # Get version type
74
76
  VERSION_TYPE="$1"
75
77
 
76
- # 获取对应版本类型的 commit 消息
78
+ # Get the commit message of the corresponding version type
77
79
  get_commit_message() {
78
80
  case "$1" in
79
81
  patch)
80
82
  echo "fix: bugfixes"
81
83
  ;;
82
84
  minor)
83
- echo "feat: 添加新功能"
85
+ echo "feat: Add new features"
84
86
  ;;
85
87
  major)
86
- echo "feat!: 重大变更"
88
+ echo "feat!: Major changes"
87
89
  ;;
88
90
  *)
89
91
  echo ""
@@ -91,62 +93,62 @@ get_commit_message() {
91
93
  esac
92
94
  }
93
95
 
94
- # 检查版本类型是否有效
96
+ # Check if the version type is valid
95
97
  COMMIT_MSG=$(get_commit_message "$VERSION_TYPE")
96
98
  if [ -z "$COMMIT_MSG" ]; then
97
- # 如果不是有效的版本类型,直接传递给 bump-my-version
99
+ # If not a valid version type, pass it directly to bump-my-version
98
100
  "$BUMP_CMD" bump "$@"
99
101
  exit 0
100
102
  fi
101
103
 
102
- # 显示当前版本
103
- echo "📦 当前版本:$("$BUMP_CMD" show current_version)"
104
+ # Show current version
105
+ echo "📦 Current version:$("$BUMP_CMD" show current_version)"
104
106
  echo ""
105
107
 
106
- # 检查是否有未提交的修改
108
+ # Check for uncommitted changes
107
109
  if [ "$AUTO_COMMIT" = true ] && [ "$DRY_RUN" = false ]; then
108
110
  if ! git diff-index --quiet HEAD -- 2>/dev/null; then
109
- echo "📝 检测到未提交的修改,准备创建 commit..."
111
+ echo "📝 Uncommitted changes detected, prepare to create commit..."
110
112
  echo ""
111
113
 
112
- echo "Commit 消息:$COMMIT_MSG"
114
+ echo "Commit information:$COMMIT_MSG"
113
115
  echo ""
114
116
 
115
- # 显示将要提交的文件
116
- echo "将要提交的文件:"
117
+ # Show files to be submitted
118
+ echo "Documents to be submitted:"
117
119
  git status --short
118
120
  echo ""
119
121
 
120
- # 提交所有修改
122
+ # Commit all changes
121
123
  git add .
122
124
  git commit -m "$COMMIT_MSG"
123
125
 
124
- echo " 代码修改已提交"
126
+ echo "OK: Code modification has been submitted"
125
127
  echo ""
126
128
  else
127
- echo "ℹ️ 没有未提交的修改,跳过自动 commit"
129
+ echo "ℹ️ No uncommitted changes, skip automatic commit"
128
130
  echo ""
129
131
  fi
130
132
  fi
131
133
 
132
- # 执行版本递增
133
- echo "🚀 开始递增版本..."
134
+ # Execution version increment
135
+ echo "🚀 Start incremental version..."
134
136
  echo ""
135
137
 
136
138
  "$BUMP_CMD" bump "$@"
137
139
 
138
140
  echo ""
139
- echo " 版本管理完成!"
141
+ echo "OK: Version management completed!"
140
142
  echo ""
141
- echo "📋 操作摘要:"
143
+ echo "📋 Operation summary:"
142
144
  if [ "$AUTO_COMMIT" = true ] && [ "$DRY_RUN" = false ]; then
143
- echo " 1. 已提交代码修改(如有)"
144
- echo " 2. 已递增版本号"
145
- echo " 3. 已创建版本 commit tag"
145
+ echo " 1. Submitted code modifications (if any)"
146
+ echo " 2. Version number incremented"
147
+ echo " 3. Version commit and tag created"
146
148
  else
147
- echo " 1. 已递增版本号"
148
- echo " 2. 已创建版本 commit tag"
149
+ echo " 1. Version number incremented"
150
+ echo " 2. Version commit and tag created"
149
151
  fi
150
152
  echo ""
151
- echo "💡 提示:如需推送到远程,请执行:"
153
+ echo "💡 Tip: To push to remote, please execute:"
152
154
  echo " git push && git push --tags"
scripts/log_writer.py CHANGED
@@ -1,14 +1,17 @@
1
1
  #!/usr/bin/env python3
2
- """面向 tmux pipe-pane 的日志写入器。
3
-
4
- 功能:
5
- - stdin 写入指定日志文件,确保主文件大小不超过阈值(默认 20MB)。
6
- - 超过阈值时将现有文件按时间戳归档,并创建新的主文件。
7
- - 定期清理超过保留时间(默认 24 小时)的归档文件。
8
-
9
- 通过环境变量控制:
10
- - MODEL_LOG_MAX_BYTES:主日志文件最大字节数,默认 20971520 (20MB)
11
- - MODEL_LOG_RETENTION_SECONDS:归档文件保留时长,默认 86400 秒 (24h)
2
+ """Log writer for tmux pipe-pane.
3
+
4
+ Features:
5
+ - Stream stdin to a target log file while keeping the primary file under a
6
+ configurable size (default 20 MB).
7
+ - When the threshold is exceeded, archive the current file with a timestamp
8
+ suffix and create a fresh primary log.
9
+ - Periodically delete archived logs that exceed the retention window
10
+ (default 24 hours).
11
+
12
+ Environment variables:
13
+ - MODEL_LOG_MAX_BYTES: maximum size of the primary log file, default 20971520 (20 MB).
14
+ - MODEL_LOG_RETENTION_SECONDS: retention period for archived files, default 86400 seconds (24 h).
12
15
  """
13
16
 
14
17
  from __future__ import annotations
@@ -25,7 +28,7 @@ DEFAULT_RETENTION_SECONDS = 24 * 60 * 60
25
28
 
26
29
 
27
30
  def _env_int(name: str, default: int) -> int:
28
- """读取整数型环境变量,解析失败时返回默认值。"""
31
+ """Read an integer environment variable with graceful fallback."""
29
32
 
30
33
  raw = os.environ.get(name)
31
34
  if raw is None:
@@ -41,7 +44,7 @@ def _env_int(name: str, default: int) -> int:
41
44
 
42
45
 
43
46
  def cleanup_archives(base_path: Path, retention_seconds: int) -> None:
44
- """删除超过保留时间的归档日志。"""
47
+ """Remove archived logs that exceed the retention period."""
45
48
 
46
49
  cutoff = time.time() - retention_seconds
47
50
  pattern = f"{base_path.stem}-*.log"
@@ -58,7 +61,7 @@ def cleanup_archives(base_path: Path, retention_seconds: int) -> None:
58
61
 
59
62
 
60
63
  def rotate_log(base_path: Path) -> Path:
61
- """将当前主日志按时间戳归档,并返回归档后的路径。"""
64
+ """Archive the current primary log and return the archive path."""
62
65
 
63
66
  timestamp = datetime.now().strftime("%Y%m%d%H%M%S")
64
67
  archive_name = f"{base_path.stem}-{timestamp}.log"
@@ -72,13 +75,13 @@ def rotate_log(base_path: Path) -> Path:
72
75
  try:
73
76
  base_path.rename(archive_path)
74
77
  except FileNotFoundError:
75
- # 文件可能已被外部删除,此时无需归档
78
+ # External processes may have removed the file; skip archival in that case.
76
79
  return archive_path
77
80
  return archive_path
78
81
 
79
82
 
80
83
  def main() -> int:
81
- """执行日志写入循环,处理滚动与归档清理。"""
84
+ """Run the logging loop, handling rotation and archive cleanup."""
82
85
 
83
86
  if len(sys.argv) != 2:
84
87
  sys.stderr.write("Usage: log_writer.py <log_file>\n")
@@ -91,7 +94,7 @@ def main() -> int:
91
94
  retention_seconds = _env_int("MODEL_LOG_RETENTION_SECONDS", DEFAULT_RETENTION_SECONDS)
92
95
 
93
96
  def open_log_file() -> tuple[int, object]:
94
- """打开日志文件并返回当前大小及文件句柄。"""
97
+ """Open the log file and return the current size together with the handle."""
95
98
 
96
99
  fp = log_path.open("ab", buffering=0)
97
100
  try: