curated_feed.config =================== .. py:module:: curated_feed.config .. autoapi-nested-parse:: Configuration loading. Settings are grouped into the same sections the TOML file uses, so a value's name in code and in the file are one and the same. All filesystem locations come from the config file (or CLI flags), never from literals in this package: the policy, corpus and state directories belong to the deployment, not to the code. Attributes ---------- .. autoapisummary:: curated_feed.config.DEFAULT_CONFIG_FILENAME Exceptions ---------- .. autoapisummary:: curated_feed.config.ConfigError Classes ------- .. autoapisummary:: curated_feed.config.Section curated_feed.config.Paths curated_feed.config.FetchSettings curated_feed.config.PaywallSettings curated_feed.config.ScoreSettings curated_feed.config.Adjustments curated_feed.config.PolicySettings curated_feed.config.PublishSettings curated_feed.config.DeploySettings curated_feed.config.Config Functions --------- .. autoapisummary:: curated_feed.config.find_config_file curated_feed.config.load_config Module Contents --------------- .. py:data:: DEFAULT_CONFIG_FILENAME :value: 'config.toml' .. py:exception:: ConfigError Bases: :py:obj:`Exception` Raised when configuration is missing or malformed. .. py:class:: Section(/, **data: Any) Bases: :py:obj:`pydantic.BaseModel` Base for every settings section: strict, and immutable once loaded. .. py:attribute:: model_config Configuration for the model, should be a dictionary conforming to [`ConfigDict`][pydantic.config.ConfigDict]. .. py:class:: Paths(/, **data: Any) Bases: :py:obj:`Section` Where everything lives. Relative entries resolve against the config file. The scoring prompt is deliberately absent. It is the engine's contract with its own parser rather than a runner's choice, so it ships with the package and no configuration can name it; `--scoring-prompt` overrides it for the length of one command. A config that still carries a `prompt` key is rejected rather than ignored, which is what makes an un-migrated runner say so instead of quietly scoring against the wrong document. .. py:attribute:: sources :type: pathlib.Path .. py:attribute:: corpus_dir :type: pathlib.Path .. py:attribute:: policy :type: pathlib.Path .. py:attribute:: state_dir :type: pathlib.Path .. py:attribute:: build_dir :type: pathlib.Path .. py:class:: FetchSettings(/, **data: Any) Bases: :py:obj:`Section` Collecting candidates. These are other people's servers: stay polite. .. py:attribute:: lookback_days :type: int :value: None .. py:attribute:: summary_max_chars :type: int :value: None .. py:attribute:: concurrency :type: int :value: None .. py:attribute:: timeout_seconds :type: float :value: None .. py:attribute:: max_retries :type: int :value: None .. py:attribute:: user_agent :type: str :value: 'curated-feed/0.1' .. py:class:: PaywallSettings(/, **data: Any) Bases: :py:obj:`Section` Checking whether a candidate's page can actually be read. Off by default: turning it on makes a request per graded article to somebody else's site, so it is a runner's decision rather than a default. Deliberately more cautious than `[fetch]` — these are article pages, and there are more of them than there are feeds. .. py:attribute:: enabled :type: bool :value: False .. py:attribute:: concurrency :type: int :value: None .. py:attribute:: timeout_seconds :type: float :value: None .. py:attribute:: max_retries :type: int :value: None .. py:attribute:: max_checks :type: int :value: None .. py:attribute:: user_agent :type: str :value: '' .. py:class:: ScoreSettings(/, **data: Any) Bases: :py:obj:`Section` Which scorer runs, and what it is allowed to spend. .. py:attribute:: provider :type: str :value: 'stub' .. py:attribute:: model :type: str :value: '' .. py:attribute:: max_tokens :type: int :value: None .. py:attribute:: effort :type: str :value: '' .. py:attribute:: timeout_seconds :type: float :value: None .. py:attribute:: stub_items :type: int :value: None .. py:class:: Adjustments(/, **data: Any) Bases: :py:obj:`Section` The weights of scoring specification §2.3, and their thresholds. .. py:attribute:: recurrence :type: float :value: 1.0 .. py:attribute:: recurrence_cap_sources :type: int :value: None .. py:attribute:: staleness :type: float :value: -1.0 .. py:attribute:: staleness_hours :type: int :value: None .. py:attribute:: penalty :type: float :value: -1.0 .. py:class:: PolicySettings(/, **data: Any) Bases: :py:obj:`Section` The arithmetic the model never sees. .. py:attribute:: floor :type: float .. py:attribute:: max_items :type: int :value: None .. py:attribute:: preference_cap :type: int :value: None .. py:attribute:: rationale_mode :type: Literal['none', 'minimal'] :value: 'minimal' .. py:attribute:: adjustments :type: Adjustments .. py:class:: PublishSettings(/, **data: Any) Bases: :py:obj:`Section` The public surface. Deliberately bland: no name, employer or project. .. py:attribute:: window :type: int :value: None .. py:attribute:: title :type: str :value: 'Curated feed' .. py:attribute:: author :type: str :value: 'curated-feed' .. py:attribute:: site_url :type: str :value: '' .. py:attribute:: path_prefix :type: str :value: '' .. py:class:: DeploySettings(/, **data: Any) Bases: :py:obj:`Section` Where the built feed is uploaded, and by what. No credential belongs here. The deploy tool resolves its own from the environment, exactly as the scorer does, so the repository never holds one. .. py:attribute:: provider :type: str :value: 'none' .. py:attribute:: project :type: str :value: '' .. py:attribute:: compatibility_date :type: str :value: None .. py:attribute:: timeout_seconds :type: float :value: None .. py:class:: Config(/, **data: Any) Bases: :py:obj:`Section` Resolved settings for a run. .. py:attribute:: paths :type: Paths .. py:attribute:: fetch :type: FetchSettings .. py:attribute:: paywall :type: PaywallSettings .. py:attribute:: score :type: ScoreSettings .. py:attribute:: policy :type: PolicySettings .. py:attribute:: publish :type: PublishSettings .. py:attribute:: deploy :type: DeploySettings .. py:method:: with_overrides(**sections: dict[str, Any] | None) -> Config Return a copy with any non-None override applied, section by section. Overrides arrive from command-line flags, where "not given" is `None` and must leave the configured value alone. .. py:function:: find_config_file(start: pathlib.Path | None = None) -> pathlib.Path Locate the config file, searching `start` and its parents. .. py:function:: load_config(config_path: pathlib.Path | None = None) -> Config Load settings from TOML. Paths are resolved relative to the config file.