Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 30 additions & 0 deletions .docs/doing/web-run-mode-helper-extraction/proposal.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
# Web Run-Mode Helper Extraction Proposal

## Why

`src/digest/web/app.py` still holds the run-mode resolution helpers and their
`RUN_MODE_OPTIONS` table as a top-of-file block. They are pure functions with no
route or `create_app` state, so they can move into a focused module — continuing
the helper-extraction pattern already applied for `feedback`, `schedule`,
`run_progress`, `sources`, and `security`.

## Scope

- Move the run-mode helpers and their config into a new `digest.web.run_mode`
module: `RUN_MODE_OPTIONS`, `DEFAULT_WEB_RUN_MODE`, `_resolve_run_mode`,
`_resolve_profile_run_mode`, `_resolve_run_mode_for_request`,
`_run_mode_options`, and `_web_live_run_options`.
- Re-import the four route-facing names (`RUN_MODE_OPTIONS`,
`_resolve_profile_run_mode`, `_resolve_run_mode_for_request`,
`_web_live_run_options`) into `digest.web.app` so existing call sites and
`from digest.web.app import ...` (used by `test_web_live_run_options`) keep
working. `DEFAULT_WEB_RUN_MODE`, `_resolve_run_mode`, and `_run_mode_options`
stay internal to the new module.
- Verify the live-run-options tests and the full backend/security checks.

## Non-goals

- No run-mode resolution behavior changes.
- No API shape changes.
- No route or scheduler-loop changes.
- No frontend changes.
17 changes: 17 additions & 0 deletions .docs/doing/web-run-mode-helper-extraction/tasks.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# Tasks

- [x] Create `digest.web.run_mode` with `RUN_MODE_OPTIONS`,
`DEFAULT_WEB_RUN_MODE`, and the five run-mode helper functions.
- [x] Remove the moved definitions from `digest.web.app` and re-import the four
route-facing names.
- [x] Confirm no circular import (`import digest.web.app` succeeds).
- [x] `ruff check src tests` is clean.
- [x] Full backend test suite passes (focus: `test_web_live_run_options`).

## Result

- `digest/web/app.py` shrank from 1751 to 1688 lines; new
`digest/web/run_mode.py` holds the 78-line module.
- Cumulative across the session's five `app.py` extractions (schedule,
run_progress, sources, security, run_mode), `app.py` dropped from 2589 to
1688 lines (-901, -35%).
75 changes: 6 additions & 69 deletions src/digest/web/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,12 @@
_feedback_features_for_source_feedback,
_feedback_rating_for_label,
)
from digest.web.run_mode import (
RUN_MODE_OPTIONS,
_resolve_profile_run_mode,
_resolve_run_mode_for_request,
_web_live_run_options,
)
from digest.web.run_progress import (
FETCH_STAGES,
_as_int,
Expand Down Expand Up @@ -82,75 +88,6 @@
)


RUN_MODE_OPTIONS: dict[str, dict[str, bool]] = {
"fresh_only": {
"use_last_completed_window": True,
"only_new": True,
"allow_seen_fallback": False,
},
"balanced": {
"use_last_completed_window": True,
"only_new": True,
"allow_seen_fallback": True,
},
"replay_recent": {
"use_last_completed_window": True,
"only_new": False,
"allow_seen_fallback": True,
},
"backfill": {
"use_last_completed_window": False,
"only_new": False,
"allow_seen_fallback": True,
},
}
DEFAULT_WEB_RUN_MODE = "fresh_only"


def _resolve_run_mode(mode: str, *, fallback: str = DEFAULT_WEB_RUN_MODE) -> str:
candidate = (mode or "").strip().lower()
if candidate in RUN_MODE_OPTIONS:
return candidate
return fallback


def _resolve_profile_run_mode(profile_cfg: Any) -> str:
run_policy = getattr(profile_cfg, "run_policy", None)
default_mode = getattr(run_policy, "default_mode", "")
return _resolve_run_mode(str(default_mode or ""), fallback=DEFAULT_WEB_RUN_MODE)


def _resolve_run_mode_for_request(
*,
profile_cfg: Any,
requested_mode: str,
) -> str:
default_mode = _resolve_profile_run_mode(profile_cfg)
run_policy = getattr(profile_cfg, "run_policy", None)
allow_override = bool(getattr(run_policy, "allow_run_override", True))
if not allow_override:
return default_mode
resolved_requested = _resolve_run_mode(requested_mode, fallback="")
if resolved_requested:
return resolved_requested
return default_mode


def _run_mode_options(mode: str) -> dict[str, bool]:
resolved_mode = _resolve_run_mode(mode, fallback=DEFAULT_WEB_RUN_MODE)
return dict(RUN_MODE_OPTIONS[resolved_mode])


def _web_live_run_options(*, trigger: str, mode: str = "") -> dict[str, bool]:
if trigger in {"web", "schedule"}:
return _run_mode_options(_resolve_run_mode(mode, fallback=DEFAULT_WEB_RUN_MODE))
return {
"use_last_completed_window": False,
"only_new": False,
"allow_seen_fallback": True,
}


@dataclass(slots=True)
class WebSettings:
sources_path: str
Expand Down
78 changes: 78 additions & 0 deletions src/digest/web/run_mode.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
"""Run-mode resolution helpers for the web control plane.

These are pure functions and configuration extracted from ``digest.web.app``;
they map run-mode names to ingestion/selection options and hold no route or
application state.
"""

from __future__ import annotations

from typing import Any

RUN_MODE_OPTIONS: dict[str, dict[str, bool]] = {
"fresh_only": {
"use_last_completed_window": True,
"only_new": True,
"allow_seen_fallback": False,
},
"balanced": {
"use_last_completed_window": True,
"only_new": True,
"allow_seen_fallback": True,
},
"replay_recent": {
"use_last_completed_window": True,
"only_new": False,
"allow_seen_fallback": True,
},
"backfill": {
"use_last_completed_window": False,
"only_new": False,
"allow_seen_fallback": True,
},
}
DEFAULT_WEB_RUN_MODE = "fresh_only"


def _resolve_run_mode(mode: str, *, fallback: str = DEFAULT_WEB_RUN_MODE) -> str:
candidate = (mode or "").strip().lower()
if candidate in RUN_MODE_OPTIONS:
return candidate
return fallback


def _resolve_profile_run_mode(profile_cfg: Any) -> str:
run_policy = getattr(profile_cfg, "run_policy", None)
default_mode = getattr(run_policy, "default_mode", "")
return _resolve_run_mode(str(default_mode or ""), fallback=DEFAULT_WEB_RUN_MODE)


def _resolve_run_mode_for_request(
*,
profile_cfg: Any,
requested_mode: str,
) -> str:
default_mode = _resolve_profile_run_mode(profile_cfg)
run_policy = getattr(profile_cfg, "run_policy", None)
allow_override = bool(getattr(run_policy, "allow_run_override", True))
if not allow_override:
return default_mode
resolved_requested = _resolve_run_mode(requested_mode, fallback="")
if resolved_requested:
return resolved_requested
return default_mode


def _run_mode_options(mode: str) -> dict[str, bool]:
resolved_mode = _resolve_run_mode(mode, fallback=DEFAULT_WEB_RUN_MODE)
return dict(RUN_MODE_OPTIONS[resolved_mode])


def _web_live_run_options(*, trigger: str, mode: str = "") -> dict[str, bool]:
if trigger in {"web", "schedule"}:
return _run_mode_options(_resolve_run_mode(mode, fallback=DEFAULT_WEB_RUN_MODE))
return {
"use_last_completed_window": False,
"only_new": False,
"allow_seen_fallback": True,
}
Loading