Files
10Backward/src/tenbackward/main.py
T

119 lines
3.1 KiB
Python

from __future__ import annotations
import sys
import time
from typing import Iterable
from . import __version__
from .config import Config, ConfigError, load_config
from .logging_setup import (
configure_json_logging,
log_error,
log_run_summary,
log_startup,
)
from .state import load_posted, save_posted
def _iter_candidates(config: Config) -> Iterable[str]:
"""Yield candidate post identifiers.
This is the extension seam for the future blog-clone + matching
pipeline. Job 1083 leaves it empty so the silent-on-no-matches
contract is the default behaviour.
"""
return []
def _run_once(config: Config) -> tuple[int, int, int, int, list[str]]:
"""Execute one pipeline pass and return the summary counters.
Returns ``(scanned, matched, posted, skipped, posted_ids)``.
"""
already_posted = set(load_posted(config.data_dir).keys())
scanned = 0
matched = 0
posted = 0
skipped = 0
posted_ids: list[str] = []
state = load_posted(config.data_dir)
for candidate_id in _iter_candidates(config):
scanned += 1
matched += 1
if candidate_id in already_posted:
skipped += 1
continue
posted_ids.append(candidate_id)
state[candidate_id] = {"posted_at": _now_iso()}
posted += 1
if posted_ids:
save_posted(config.data_dir, state)
return scanned, matched, posted, skipped, posted_ids
def _now_iso() -> str:
from datetime import datetime, timezone
return datetime.now(tz=timezone.utc).isoformat()
def _run_with_retry(config: Config) -> tuple[int, int, int, int, list[str]] | None:
"""Execute the pipeline with retries. Returns the summary counters on
success or ``None`` when the retry budget is exhausted."""
attempts = max(1, config.max_retries + 1)
last_exc: BaseException | None = None
for attempt in range(1, attempts + 1):
try:
return _run_once(config)
except Exception as exc: # noqa: BLE001 — broad on purpose
last_exc = exc
log_error(
"pipeline_error",
exc=exc,
attempt=attempt,
max_attempts=attempts,
)
if attempt < attempts:
time.sleep(0)
assert last_exc is not None
log_error("pipeline_failed", exc=last_exc, attempts=attempts)
return None
def main() -> int:
configure_json_logging()
try:
config = load_config()
except ConfigError as exc:
log_error("configuration_error", exc=exc)
return 2
config.data_dir.mkdir(parents=True, exist_ok=True)
log_startup(
version=__version__,
site_url=config.site_url,
run_at=config.run_at,
tz=config.tz,
hashtags=config.hashtags,
throwback_prefix=config.throwback_prefix,
)
result = _run_with_retry(config)
if result is None:
return 1
scanned, matched, posted, skipped, posted_ids = result
log_run_summary(scanned, matched, posted, skipped, posted_ids)
return 0
if __name__ == "__main__":
sys.exit(main())