feat: Environment Configuration and Logging Baseline
This commit is contained in:
+92
-23
@@ -1,47 +1,116 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
import sys
|
||||
import time
|
||||
from typing import Iterable
|
||||
|
||||
from . import __version__
|
||||
from .config import ConfigError, load_config
|
||||
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
|
||||
|
||||
|
||||
log = logging.getLogger("tenbackward")
|
||||
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:
|
||||
logging.basicConfig(
|
||||
level=logging.INFO,
|
||||
format="%(asctime)s %(levelname)s %(name)s: %(message)s",
|
||||
)
|
||||
configure_json_logging()
|
||||
|
||||
try:
|
||||
config = load_config()
|
||||
except ConfigError as exc:
|
||||
log.error("configuration error: %s", exc)
|
||||
log_error("configuration_error", exc=exc)
|
||||
return 2
|
||||
|
||||
config.data_dir.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
state = load_posted(config.data_dir)
|
||||
save_posted(config.data_dir, state)
|
||||
|
||||
log.info(
|
||||
"10backward v%s ready (site=%s, run_at=%s, tz=%s, hashtags=%s, prefix=%r)",
|
||||
__version__,
|
||||
config.site_url,
|
||||
config.run_at,
|
||||
config.tz,
|
||||
config.hashtags,
|
||||
config.throwback_prefix,
|
||||
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,
|
||||
)
|
||||
|
||||
log.warning(
|
||||
"post pipeline is not yet implemented; this run only validates the scaffold. "
|
||||
"Future job will clone the blog and post a throwback via Mastodon.py."
|
||||
)
|
||||
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
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user