curated_feed.cli

One command that runs the daily job end to end.

Each stage is invoked exactly as it would be on its own, so the chain reads and writes the same artifacts a replay does. Nothing here knows how a stage works; going through the files is the design rather than an inefficiency, and it means this module cannot drift away from the commands it calls.

–date selects a past collection day, which is also what replay means: a day already fetched can be re-scored, re-selected and re-rendered as often as a policy is edited, so tuning is a loop of seconds. Fetching is therefore skipped whenever a date is given — yesterday’s feeds no longer hold yesterday’s news.

Publishing is the one stage that is opt-in. Replaying a saved day is the commonest thing to run by hand, and it is usually run against the stub scorer; having that reach the internet by default would be a footgun with no upside, since the only run that should publish is the scheduled one.

Attributes

Functions

first_stage(→ str)

Where to start: what was asked for, or what the arguments imply.

last_stage(→ str)

Where to stop: rendering, unless publishing was asked for.

stage_argv(→ list[str])

The arguments this stage would have been given on its own.

build_parser(→ argparse.ArgumentParser)

main(→ int)

Module Contents

curated_feed.cli.STAGES = ('fetch', 'score', 'paywall', 'select', 'render', 'deploy')
curated_feed.cli.DATED_STAGES
curated_feed.cli.PUBLISHING_STAGE = 'deploy'
curated_feed.cli.MAIN_BY_STAGE: dict[str, collections.abc.Callable[[list[str]], int]]
curated_feed.cli.first_stage(args: argparse.Namespace) → str[source]

Where to start: what was asked for, or what the arguments imply.

>>> first_stage(argparse.Namespace(from_stage=None, date=None))
'fetch'
>>> first_stage(argparse.Namespace(from_stage=None, date=date(2026, 8, 8)))
'score'
curated_feed.cli.last_stage(args: argparse.Namespace) → str[source]

Where to stop: rendering, unless publishing was asked for.

Starting at the publishing stage is asking for it too, since there is nothing else left to do.

>>> last_stage(argparse.Namespace(deploy=False, from_stage=None))
'render'
>>> last_stage(argparse.Namespace(deploy=True, from_stage=None))
'deploy'
>>> last_stage(argparse.Namespace(deploy=False, from_stage='deploy'))
'deploy'
curated_feed.cli.stage_argv(stage: str, args: argparse.Namespace) → list[str][source]

The arguments this stage would have been given on its own.

curated_feed.cli.build_parser() → argparse.ArgumentParser[source]
curated_feed.cli.main(argv: collections.abc.Sequence[str] | None = None) → int[source]