Skip to content

Commit 9010cbd

Browse files
takemi-ohamaclaude
andcommitted
refactor(container): レビュー指摘の細部対応 (datetime import, env var, regex)
PR #7 の self-review で挙げた MED/LOW 指摘を反映: - [MED] datetime / timezone / re を関数内 import からモジュール先頭へ移動 Python 標準ライブラリの軽量モジュールを関数内 import するのは非慣用。 - [MED] 閾値を環境変数 DEVBASE_IMAGE_MAX_AGE_DAYS で上書き可能に _image_max_age_days() ヘルパを追加。不正値 (非数値・負数) は 警告ログを出してデフォルト 7 にフォールバック。 - [LOW] RFC3339 タイムスタンプ整形を re.sub で簡潔化 split('.') + rfind('+/-') の手作業ロジックを 1 行の正規表現に。 ナノ秒/マイクロ秒/ミリ秒/小数なし/オフセット付き全パターン動作確認済み。 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 6859672 commit 9010cbd

1 file changed

Lines changed: 36 additions & 20 deletions

File tree

lib/devbase/commands/container.py

Lines changed: 36 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,10 @@
22

33
import json
44
import os
5+
import re
56
import subprocess
67
import sys
8+
from datetime import datetime, timezone
79
from pathlib import Path
810
from typing import Optional
911

@@ -405,18 +407,40 @@ def _ensure_env_files() -> bool:
405407
return success
406408

407409

408-
_IMAGE_MAX_AGE_DAYS = 7
410+
_IMAGE_MAX_AGE_DAYS_DEFAULT = 7
411+
412+
413+
def _image_max_age_days() -> int:
414+
"""Threshold for triggering an image rebuild/pull.
415+
416+
Override via the DEVBASE_IMAGE_MAX_AGE_DAYS environment variable.
417+
Falls back to the default on missing or malformed values.
418+
"""
419+
raw = os.environ.get('DEVBASE_IMAGE_MAX_AGE_DAYS')
420+
if not raw:
421+
return _IMAGE_MAX_AGE_DAYS_DEFAULT
422+
try:
423+
value = int(raw)
424+
if value < 0:
425+
raise ValueError
426+
return value
427+
except ValueError:
428+
logger.warning(
429+
"Invalid DEVBASE_IMAGE_MAX_AGE_DAYS=%r, using default %d",
430+
raw, _IMAGE_MAX_AGE_DAYS_DEFAULT
431+
)
432+
return _IMAGE_MAX_AGE_DAYS_DEFAULT
409433

410434

411435
def _ensure_images() -> bool:
412436
"""Check that required container images exist and are fresh.
413437
414-
Behavior:
438+
Behavior (threshold = DEVBASE_IMAGE_MAX_AGE_DAYS or 7):
415439
- Image missing + has build: → run `devbase build`
416440
- Image missing + image-only (no build:) → run `docker pull`
417-
- Image present and >= _IMAGE_MAX_AGE_DAYS old + has build:
441+
- Image present and >= threshold days old + has build:
418442
→ rebuild with `--no-cache`
419-
- Image present and >= _IMAGE_MAX_AGE_DAYS old + image-only
443+
- Image present and >= threshold days old + image-only
420444
→ re-pull
421445
- Otherwise: nothing to do
422446
"""
@@ -465,13 +489,14 @@ def _ensure_images() -> bool:
465489
logger.info("Container image '%s' not found, pulling...", image_name)
466490
return _run_pull(image_name)
467491

492+
max_age = _image_max_age_days()
468493
age_days = _get_image_age_days(inspect.stdout)
469-
if age_days is None or age_days < _IMAGE_MAX_AGE_DAYS:
494+
if age_days is None or age_days < max_age:
470495
return True
471496

472497
logger.info(
473498
"Container image '%s' is %d days old (>= %d days threshold)",
474-
image_name, age_days, _IMAGE_MAX_AGE_DAYS
499+
image_name, age_days, max_age
475500
)
476501
if has_build:
477502
logger.info("Rebuilding with --no-cache...")
@@ -494,21 +519,12 @@ def _get_image_age_days(inspect_json: str) -> Optional[int]:
494519
created = data[0].get('Created', '')
495520
if not created:
496521
return None
497-
from datetime import datetime, timezone
498522
# Docker's 'Created' is RFC3339 with nanoseconds, e.g.
499-
# '2024-01-15T10:30:00.123456789Z'. fromisoformat (Python 3.10) does not
500-
# accept nanoseconds, so trim to microseconds and convert 'Z' to +00:00.
501-
ts = created.replace('Z', '+00:00')
502-
if '.' in ts:
503-
head, frac = ts.split('.', 1)
504-
tz_idx = max(frac.rfind('+'), frac.rfind('-'))
505-
if tz_idx >= 0:
506-
frac, tz = frac[:tz_idx], frac[tz_idx:]
507-
else:
508-
tz = ''
509-
ts = f"{head}.{frac[:6]}{tz}"
510-
dt = datetime.fromisoformat(ts)
511-
delta = datetime.now(timezone.utc) - dt
523+
# '2024-01-15T10:30:00.123456789Z'. Python 3.10's fromisoformat does
524+
# not accept nanoseconds, so trim fractional seconds to 6 digits and
525+
# normalize 'Z' to '+00:00'.
526+
ts = re.sub(r'(\.\d{6})\d+', r'\1', created.replace('Z', '+00:00'))
527+
delta = datetime.now(timezone.utc) - datetime.fromisoformat(ts)
512528
return delta.days
513529
except Exception as e:
514530
logger.warning("Could not parse image creation date: %s", e)

0 commit comments

Comments
 (0)